From af99730382c98014dd922a25e3e27acb70bc5a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 23 Oct 2023 13:11:59 +0200 Subject: [PATCH 1/9] Delete qiskit.algorithms and tests --- qiskit/algorithms/__init__.py | 430 --------- qiskit/algorithms/algorithm_job.py | 24 - qiskit/algorithms/algorithm_result.py | 65 -- .../amplitude_amplifiers/__init__.py | 25 - .../amplification_problem.py | 213 ----- .../amplitude_amplifier.py | 127 --- .../algorithms/amplitude_amplifiers/grover.py | 449 --------- .../amplitude_estimators/__init__.py | 34 - qiskit/algorithms/amplitude_estimators/ae.py | 688 -------------- .../amplitude_estimators/ae_utils.py | 258 ------ .../amplitude_estimator.py | 131 --- .../estimation_problem.py | 274 ------ qiskit/algorithms/amplitude_estimators/fae.py | 391 -------- qiskit/algorithms/amplitude_estimators/iae.py | 684 -------------- .../algorithms/amplitude_estimators/mlae.py | 667 -------------- qiskit/algorithms/aux_ops_evaluator.py | 195 ---- qiskit/algorithms/eigen_solvers/__init__.py | 19 - .../algorithms/eigen_solvers/eigen_solver.py | 134 --- .../eigen_solvers/numpy_eigen_solver.py | 278 ------ qiskit/algorithms/eigen_solvers/vqd.py | 807 ----------------- qiskit/algorithms/eigensolvers/__init__.py | 53 -- qiskit/algorithms/eigensolvers/eigensolver.py | 106 --- .../eigensolvers/numpy_eigensolver.py | 327 ------- qiskit/algorithms/eigensolvers/vqd.py | 542 ----------- qiskit/algorithms/evolvers/__init__.py | 21 - .../algorithms/evolvers/evolution_problem.py | 122 --- .../algorithms/evolvers/evolution_result.py | 55 -- .../algorithms/evolvers/imaginary_evolver.py | 55 -- qiskit/algorithms/evolvers/real_evolver.py | 55 -- .../evolvers/trotterization/__init__.py | 21 - .../evolvers/trotterization/trotter_qrte.py | 262 ------ qiskit/algorithms/exceptions.py | 21 - qiskit/algorithms/gradients/__init__.py | 130 --- qiskit/algorithms/gradients/base/__init__.py | 11 - .../gradients/base/base_estimator_gradient.py | 360 -------- qiskit/algorithms/gradients/base/base_qgt.py | 383 -------- .../gradients/base/base_sampler_gradient.py | 296 ------ .../base/estimator_gradient_result.py | 35 - .../algorithms/gradients/base/qgt_result.py | 39 - .../gradients/base/sampler_gradient_result.py | 33 - .../gradients/finite_diff/__init__.py | 11 - .../finite_diff_estimator_gradient.py | 148 --- .../finite_diff_sampler_gradient.py | 161 ---- .../algorithms/gradients/lin_comb/__init__.py | 11 - .../lin_comb/lin_comb_estimator_gradient.py | 195 ---- .../gradients/lin_comb/lin_comb_qgt.py | 257 ------ .../lin_comb/lin_comb_sampler_gradient.py | 147 --- .../gradients/param_shift/__init__.py | 11 - .../param_shift_estimator_gradient.py | 123 --- .../param_shift_sampler_gradient.py | 117 --- qiskit/algorithms/gradients/qfi.py | 171 ---- qiskit/algorithms/gradients/qfi_result.py | 35 - .../algorithms/gradients/reverse/__init__.py | 11 - qiskit/algorithms/gradients/reverse/bind.py | 53 -- .../gradients/reverse/derive_circuit.py | 157 ---- .../gradients/reverse/reverse_gradient.py | 200 ---- .../gradients/reverse/reverse_qgt.py | 252 ------ .../gradients/reverse/split_circuits.py | 68 -- qiskit/algorithms/gradients/spsa/__init__.py | 11 - .../gradients/spsa/spsa_estimator_gradient.py | 135 --- .../gradients/spsa/spsa_sampler_gradient.py | 136 --- qiskit/algorithms/gradients/utils.py | 375 -------- qiskit/algorithms/list_or_dict.py | 18 - .../minimum_eigen_solvers/__init__.py | 27 - .../minimum_eigen_solver.py | 141 --- .../numpy_minimum_eigen_solver.py | 105 --- .../algorithms/minimum_eigen_solvers/qaoa.py | 185 ---- .../algorithms/minimum_eigen_solvers/vqe.py | 749 --------------- .../minimum_eigensolvers/__init__.py | 66 -- .../minimum_eigensolvers/adapt_vqe.py | 419 --------- .../diagonal_estimator.py | 199 ---- .../minimum_eigensolver.py | 97 -- .../numpy_minimum_eigensolver.py | 106 --- .../algorithms/minimum_eigensolvers/qaoa.py | 144 --- .../minimum_eigensolvers/sampling_mes.py | 138 --- .../minimum_eigensolvers/sampling_vqe.py | 382 -------- qiskit/algorithms/minimum_eigensolvers/vqe.py | 356 -------- qiskit/algorithms/observables_evaluator.py | 126 --- qiskit/algorithms/optimizers/__init__.py | 182 ---- qiskit/algorithms/optimizers/adam_amsgrad.py | 270 ------ qiskit/algorithms/optimizers/aqgd.py | 367 -------- qiskit/algorithms/optimizers/bobyqa.py | 84 -- qiskit/algorithms/optimizers/cg.py | 70 -- qiskit/algorithms/optimizers/cobyla.py | 59 -- .../algorithms/optimizers/gradient_descent.py | 401 -------- qiskit/algorithms/optimizers/gsls.py | 378 -------- qiskit/algorithms/optimizers/imfil.py | 86 -- qiskit/algorithms/optimizers/l_bfgs_b.py | 88 -- qiskit/algorithms/optimizers/nelder_mead.py | 73 -- qiskit/algorithms/optimizers/nft.py | 170 ---- .../algorithms/optimizers/nlopts/__init__.py | 13 - qiskit/algorithms/optimizers/nlopts/crs.py | 35 - .../algorithms/optimizers/nlopts/direct_l.py | 34 - .../optimizers/nlopts/direct_l_rand.py | 32 - qiskit/algorithms/optimizers/nlopts/esch.py | 33 - qiskit/algorithms/optimizers/nlopts/isres.py | 39 - .../optimizers/nlopts/nloptimizer.py | 131 --- qiskit/algorithms/optimizers/optimizer.py | 389 -------- .../optimizers/optimizer_utils/__init__.py | 27 - .../optimizer_utils/learning_rate.py | 88 -- qiskit/algorithms/optimizers/p_bfgs.py | 182 ---- qiskit/algorithms/optimizers/powell.py | 64 -- qiskit/algorithms/optimizers/qnspsa.py | 421 --------- .../algorithms/optimizers/scipy_optimizer.py | 183 ---- qiskit/algorithms/optimizers/slsqp.py | 73 -- qiskit/algorithms/optimizers/snobfit.py | 130 --- qiskit/algorithms/optimizers/spsa.py | 810 ----------------- .../optimizers/steppable_optimizer.py | 303 ------- qiskit/algorithms/optimizers/tnc.py | 83 -- qiskit/algorithms/optimizers/umda.py | 355 -------- .../algorithms/phase_estimators/__init__.py | 31 - .../hamiltonian_phase_estimation.py | 309 ------- .../hamiltonian_phase_estimation_result.py | 108 --- qiskit/algorithms/phase_estimators/ipe.py | 229 ----- .../phase_estimators/phase_estimation.py | 268 ------ .../phase_estimation_result.py | 174 ---- .../phase_estimation_scale.py | 160 ---- .../phase_estimators/phase_estimator.py | 58 -- .../algorithms/state_fidelities/__init__.py | 42 - .../state_fidelities/base_state_fidelity.py | 308 ------- .../state_fidelities/compute_uncompute.py | 249 ----- .../state_fidelities/state_fidelity_result.py | 37 - qiskit/algorithms/time_evolvers/__init__.py | 38 - .../classical_methods/__init__.py | 18 - .../time_evolvers/classical_methods/evolve.py | 219 ----- .../scipy_imaginary_evolver.py | 51 -- .../classical_methods/scipy_real_evolver.py | 50 - .../time_evolvers/imaginary_time_evolver.py | 37 - .../algorithms/time_evolvers/pvqd/__init__.py | 18 - qiskit/algorithms/time_evolvers/pvqd/pvqd.py | 435 --------- .../time_evolvers/pvqd/pvqd_result.py | 54 -- qiskit/algorithms/time_evolvers/pvqd/utils.py | 109 --- .../time_evolvers/real_time_evolver.py | 37 - .../time_evolvers/time_evolution_problem.py | 114 --- .../time_evolvers/time_evolution_result.py | 60 -- .../time_evolvers/trotterization/__init__.py | 29 - .../trotterization/trotter_qrte.py | 246 ----- .../time_evolvers/variational/__init__.py | 117 --- .../variational/solvers/__init__.py | 48 - .../variational/solvers/ode/__init__.py | 13 - .../solvers/ode/abstract_ode_function.py | 51 -- .../solvers/ode/forward_euler_solver.py | 72 -- .../variational/solvers/ode/ode_function.py | 41 - .../solvers/ode/ode_function_factory.py | 72 -- .../solvers/ode/var_qte_ode_solver.py | 89 -- .../solvers/var_qte_linear_solver.py | 129 --- .../time_evolvers/variational/var_qite.py | 120 --- .../time_evolvers/variational/var_qrte.py | 122 --- .../time_evolvers/variational/var_qte.py | 290 ------ .../variational/var_qte_result.py | 56 -- .../variational_principles/__init__.py | 27 - .../imaginary_mc_lachlan_principle.py | 128 --- .../imaginary_variational_principle.py | 22 - .../real_mc_lachlan_principle.py | 166 ---- .../real_variational_principle.py | 22 - .../variational_principle.py | 98 -- qiskit/algorithms/utils/__init__.py | 21 - qiskit/algorithms/utils/set_batching.py | 27 - qiskit/algorithms/utils/validate_bounds.py | 44 - .../utils/validate_initial_point.py | 72 -- qiskit/algorithms/variational_algorithm.py | 137 --- test/python/algorithms/__init__.py | 17 - .../python/algorithms/algorithms_test_case.py | 21 - .../algorithms/eigensolvers/__init__.py | 13 - .../eigensolvers/test_numpy_eigensolver.py | 216 ----- .../algorithms/eigensolvers/test_vqd.py | 453 --------- test/python/algorithms/evolvers/__init__.py | 11 - .../evolvers/test_evolution_problem.py | 136 --- .../evolvers/test_evolution_result.py | 50 - .../evolvers/trotterization/__init__.py | 11 - .../trotterization/test_trotter_qrte.py | 259 ------ test/python/algorithms/gradients/__init__.py | 13 - .../gradients/logging_primitives.py | 42 - .../gradients/test_estimator_gradient.py | 519 ----------- test/python/algorithms/gradients/test_qfi.py | 151 --- test/python/algorithms/gradients/test_qgt.py | 309 ------- .../gradients/test_sampler_gradient.py | 690 -------------- .../minimum_eigensolvers/__init__.py | 11 - .../minimum_eigensolvers/test_adapt_vqe.py | 245 ----- .../test_numpy_minimum_eigensolver.py | 240 ----- .../minimum_eigensolvers/test_qaoa.py | 304 ------- .../minimum_eigensolvers/test_qaoa_opflow.py | 312 ------- .../minimum_eigensolvers/test_sampling_vqe.py | 287 ------ .../minimum_eigensolvers/test_vqe.py | 488 ---------- test/python/algorithms/optimizers/__init__.py | 13 - .../optimizers/test_gradient_descent.py | 196 ---- .../optimizers/test_optimizer_aqgd.py | 122 --- .../optimizers/test_optimizer_nft.py | 64 -- .../algorithms/optimizers/test_optimizers.py | 404 --------- .../optimizers/test_optimizers_scikitquant.py | 118 --- .../python/algorithms/optimizers/test_spsa.py | 293 ------ .../python/algorithms/optimizers/test_umda.py | 98 -- .../algorithms/optimizers/utils/__init__.py | 12 - .../optimizers/utils/test_learning_rate.py | 54 -- .../algorithms/state_fidelities/__init__.py | 13 - .../test_compute_uncompute.py | 264 ------ .../algorithms/test_amplitude_estimators.py | 724 --------------- .../algorithms/test_aux_ops_evaluator.py | 197 ---- test/python/algorithms/test_backendv1.py | 148 --- test/python/algorithms/test_backendv2.py | 102 --- test/python/algorithms/test_entangler_map.py | 68 -- test/python/algorithms/test_grover.py | 402 -------- .../test_measure_error_mitigation.py | 524 ----------- .../algorithms/test_numpy_eigen_solver.py | 210 ----- .../test_numpy_minimum_eigen_solver.py | 277 ------ .../algorithms/test_observables_evaluator.py | 189 ---- .../python/algorithms/test_phase_estimator.py | 665 -------------- test/python/algorithms/test_qaoa.py | 410 --------- .../algorithms/test_skip_qobj_validation.py | 148 --- test/python/algorithms/test_validation.py | 94 -- test/python/algorithms/test_vqd.py | 663 -------------- test/python/algorithms/test_vqe.py | 856 ------------------ .../algorithms/time_evolvers/__init__.py | 11 - .../classical_methods/__init__.py | 11 - .../test_scipy_imaginary_evolver.py | 183 ---- .../test_scipy_real_evolver.py | 154 ---- .../algorithms/time_evolvers/test_pvqd.py | 342 ------- .../test_time_evolution_problem.py | 98 -- .../test_time_evolution_result.py | 47 - .../time_evolvers/test_trotter_qrte.py | 273 ------ .../time_evolvers/variational/__init__.py | 11 - .../variational/solvers/__init__.py | 11 - .../solvers/expected_results/__init__.py | 12 - .../test_varqte_linear_solver_expected_1.py | 182 ---- .../variational/solvers/ode/__init__.py | 11 - .../solvers/ode/test_forward_euler_solver.py | 47 - .../solvers/ode/test_ode_function.py | 147 --- .../solvers/ode/test_var_qte_ode_solver.py | 127 --- .../solvers/test_varqte_linear_solver.py | 112 --- .../variational/test_var_qite.py | 333 ------- .../variational/test_var_qrte.py | 319 ------- .../time_evolvers/variational/test_var_qte.py | 84 -- .../variational_principles/__init__.py | 11 - .../expected_results/__init__.py | 12 - ...lachlan_variational_principle_expected1.py | 182 ---- ...lachlan_variational_principle_expected2.py | 182 ---- ...lachlan_variational_principle_expected3.py | 182 ---- .../imaginary/__init__.py | 11 - .../test_imaginary_mc_lachlan_principle.py | 115 --- .../variational_principles/real/__init__.py | 11 - .../real/test_real_mc_lachlan_principle.py | 120 --- test/python/algorithms/utils/__init__.py | 11 - .../algorithms/utils/test_validate_bounds.py | 57 -- .../utils/test_validate_initial_point.py | 54 -- 244 files changed, 41577 deletions(-) delete mode 100644 qiskit/algorithms/__init__.py delete mode 100644 qiskit/algorithms/algorithm_job.py delete mode 100644 qiskit/algorithms/algorithm_result.py delete mode 100644 qiskit/algorithms/amplitude_amplifiers/__init__.py delete mode 100644 qiskit/algorithms/amplitude_amplifiers/amplification_problem.py delete mode 100644 qiskit/algorithms/amplitude_amplifiers/amplitude_amplifier.py delete mode 100644 qiskit/algorithms/amplitude_amplifiers/grover.py delete mode 100644 qiskit/algorithms/amplitude_estimators/__init__.py delete mode 100644 qiskit/algorithms/amplitude_estimators/ae.py delete mode 100644 qiskit/algorithms/amplitude_estimators/ae_utils.py delete mode 100644 qiskit/algorithms/amplitude_estimators/amplitude_estimator.py delete mode 100644 qiskit/algorithms/amplitude_estimators/estimation_problem.py delete mode 100644 qiskit/algorithms/amplitude_estimators/fae.py delete mode 100644 qiskit/algorithms/amplitude_estimators/iae.py delete mode 100644 qiskit/algorithms/amplitude_estimators/mlae.py delete mode 100644 qiskit/algorithms/aux_ops_evaluator.py delete mode 100644 qiskit/algorithms/eigen_solvers/__init__.py delete mode 100644 qiskit/algorithms/eigen_solvers/eigen_solver.py delete mode 100644 qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py delete mode 100644 qiskit/algorithms/eigen_solvers/vqd.py delete mode 100644 qiskit/algorithms/eigensolvers/__init__.py delete mode 100644 qiskit/algorithms/eigensolvers/eigensolver.py delete mode 100644 qiskit/algorithms/eigensolvers/numpy_eigensolver.py delete mode 100644 qiskit/algorithms/eigensolvers/vqd.py delete mode 100644 qiskit/algorithms/evolvers/__init__.py delete mode 100644 qiskit/algorithms/evolvers/evolution_problem.py delete mode 100644 qiskit/algorithms/evolvers/evolution_result.py delete mode 100644 qiskit/algorithms/evolvers/imaginary_evolver.py delete mode 100644 qiskit/algorithms/evolvers/real_evolver.py delete mode 100644 qiskit/algorithms/evolvers/trotterization/__init__.py delete mode 100644 qiskit/algorithms/evolvers/trotterization/trotter_qrte.py delete mode 100644 qiskit/algorithms/exceptions.py delete mode 100644 qiskit/algorithms/gradients/__init__.py delete mode 100644 qiskit/algorithms/gradients/base/__init__.py delete mode 100644 qiskit/algorithms/gradients/base/base_estimator_gradient.py delete mode 100644 qiskit/algorithms/gradients/base/base_qgt.py delete mode 100644 qiskit/algorithms/gradients/base/base_sampler_gradient.py delete mode 100644 qiskit/algorithms/gradients/base/estimator_gradient_result.py delete mode 100644 qiskit/algorithms/gradients/base/qgt_result.py delete mode 100644 qiskit/algorithms/gradients/base/sampler_gradient_result.py delete mode 100644 qiskit/algorithms/gradients/finite_diff/__init__.py delete mode 100644 qiskit/algorithms/gradients/finite_diff/finite_diff_estimator_gradient.py delete mode 100644 qiskit/algorithms/gradients/finite_diff/finite_diff_sampler_gradient.py delete mode 100644 qiskit/algorithms/gradients/lin_comb/__init__.py delete mode 100644 qiskit/algorithms/gradients/lin_comb/lin_comb_estimator_gradient.py delete mode 100644 qiskit/algorithms/gradients/lin_comb/lin_comb_qgt.py delete mode 100644 qiskit/algorithms/gradients/lin_comb/lin_comb_sampler_gradient.py delete mode 100644 qiskit/algorithms/gradients/param_shift/__init__.py delete mode 100644 qiskit/algorithms/gradients/param_shift/param_shift_estimator_gradient.py delete mode 100644 qiskit/algorithms/gradients/param_shift/param_shift_sampler_gradient.py delete mode 100644 qiskit/algorithms/gradients/qfi.py delete mode 100644 qiskit/algorithms/gradients/qfi_result.py delete mode 100644 qiskit/algorithms/gradients/reverse/__init__.py delete mode 100644 qiskit/algorithms/gradients/reverse/bind.py delete mode 100644 qiskit/algorithms/gradients/reverse/derive_circuit.py delete mode 100644 qiskit/algorithms/gradients/reverse/reverse_gradient.py delete mode 100644 qiskit/algorithms/gradients/reverse/reverse_qgt.py delete mode 100644 qiskit/algorithms/gradients/reverse/split_circuits.py delete mode 100644 qiskit/algorithms/gradients/spsa/__init__.py delete mode 100644 qiskit/algorithms/gradients/spsa/spsa_estimator_gradient.py delete mode 100644 qiskit/algorithms/gradients/spsa/spsa_sampler_gradient.py delete mode 100644 qiskit/algorithms/gradients/utils.py delete mode 100644 qiskit/algorithms/list_or_dict.py delete mode 100644 qiskit/algorithms/minimum_eigen_solvers/__init__.py delete mode 100644 qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py delete mode 100644 qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py delete mode 100644 qiskit/algorithms/minimum_eigen_solvers/qaoa.py delete mode 100644 qiskit/algorithms/minimum_eigen_solvers/vqe.py delete mode 100644 qiskit/algorithms/minimum_eigensolvers/__init__.py delete mode 100644 qiskit/algorithms/minimum_eigensolvers/adapt_vqe.py delete mode 100644 qiskit/algorithms/minimum_eigensolvers/diagonal_estimator.py delete mode 100644 qiskit/algorithms/minimum_eigensolvers/minimum_eigensolver.py delete mode 100644 qiskit/algorithms/minimum_eigensolvers/numpy_minimum_eigensolver.py delete mode 100644 qiskit/algorithms/minimum_eigensolvers/qaoa.py delete mode 100644 qiskit/algorithms/minimum_eigensolvers/sampling_mes.py delete mode 100644 qiskit/algorithms/minimum_eigensolvers/sampling_vqe.py delete mode 100644 qiskit/algorithms/minimum_eigensolvers/vqe.py delete mode 100644 qiskit/algorithms/observables_evaluator.py delete mode 100644 qiskit/algorithms/optimizers/__init__.py delete mode 100644 qiskit/algorithms/optimizers/adam_amsgrad.py delete mode 100644 qiskit/algorithms/optimizers/aqgd.py delete mode 100644 qiskit/algorithms/optimizers/bobyqa.py delete mode 100644 qiskit/algorithms/optimizers/cg.py delete mode 100644 qiskit/algorithms/optimizers/cobyla.py delete mode 100644 qiskit/algorithms/optimizers/gradient_descent.py delete mode 100644 qiskit/algorithms/optimizers/gsls.py delete mode 100644 qiskit/algorithms/optimizers/imfil.py delete mode 100644 qiskit/algorithms/optimizers/l_bfgs_b.py delete mode 100644 qiskit/algorithms/optimizers/nelder_mead.py delete mode 100644 qiskit/algorithms/optimizers/nft.py delete mode 100644 qiskit/algorithms/optimizers/nlopts/__init__.py delete mode 100644 qiskit/algorithms/optimizers/nlopts/crs.py delete mode 100644 qiskit/algorithms/optimizers/nlopts/direct_l.py delete mode 100644 qiskit/algorithms/optimizers/nlopts/direct_l_rand.py delete mode 100644 qiskit/algorithms/optimizers/nlopts/esch.py delete mode 100644 qiskit/algorithms/optimizers/nlopts/isres.py delete mode 100644 qiskit/algorithms/optimizers/nlopts/nloptimizer.py delete mode 100644 qiskit/algorithms/optimizers/optimizer.py delete mode 100644 qiskit/algorithms/optimizers/optimizer_utils/__init__.py delete mode 100644 qiskit/algorithms/optimizers/optimizer_utils/learning_rate.py delete mode 100644 qiskit/algorithms/optimizers/p_bfgs.py delete mode 100644 qiskit/algorithms/optimizers/powell.py delete mode 100644 qiskit/algorithms/optimizers/qnspsa.py delete mode 100644 qiskit/algorithms/optimizers/scipy_optimizer.py delete mode 100644 qiskit/algorithms/optimizers/slsqp.py delete mode 100644 qiskit/algorithms/optimizers/snobfit.py delete mode 100644 qiskit/algorithms/optimizers/spsa.py delete mode 100644 qiskit/algorithms/optimizers/steppable_optimizer.py delete mode 100644 qiskit/algorithms/optimizers/tnc.py delete mode 100644 qiskit/algorithms/optimizers/umda.py delete mode 100644 qiskit/algorithms/phase_estimators/__init__.py delete mode 100644 qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py delete mode 100644 qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py delete mode 100644 qiskit/algorithms/phase_estimators/ipe.py delete mode 100644 qiskit/algorithms/phase_estimators/phase_estimation.py delete mode 100644 qiskit/algorithms/phase_estimators/phase_estimation_result.py delete mode 100644 qiskit/algorithms/phase_estimators/phase_estimation_scale.py delete mode 100644 qiskit/algorithms/phase_estimators/phase_estimator.py delete mode 100644 qiskit/algorithms/state_fidelities/__init__.py delete mode 100644 qiskit/algorithms/state_fidelities/base_state_fidelity.py delete mode 100644 qiskit/algorithms/state_fidelities/compute_uncompute.py delete mode 100644 qiskit/algorithms/state_fidelities/state_fidelity_result.py delete mode 100644 qiskit/algorithms/time_evolvers/__init__.py delete mode 100644 qiskit/algorithms/time_evolvers/classical_methods/__init__.py delete mode 100644 qiskit/algorithms/time_evolvers/classical_methods/evolve.py delete mode 100644 qiskit/algorithms/time_evolvers/classical_methods/scipy_imaginary_evolver.py delete mode 100644 qiskit/algorithms/time_evolvers/classical_methods/scipy_real_evolver.py delete mode 100644 qiskit/algorithms/time_evolvers/imaginary_time_evolver.py delete mode 100644 qiskit/algorithms/time_evolvers/pvqd/__init__.py delete mode 100644 qiskit/algorithms/time_evolvers/pvqd/pvqd.py delete mode 100644 qiskit/algorithms/time_evolvers/pvqd/pvqd_result.py delete mode 100644 qiskit/algorithms/time_evolvers/pvqd/utils.py delete mode 100644 qiskit/algorithms/time_evolvers/real_time_evolver.py delete mode 100644 qiskit/algorithms/time_evolvers/time_evolution_problem.py delete mode 100644 qiskit/algorithms/time_evolvers/time_evolution_result.py delete mode 100644 qiskit/algorithms/time_evolvers/trotterization/__init__.py delete mode 100644 qiskit/algorithms/time_evolvers/trotterization/trotter_qrte.py delete mode 100644 qiskit/algorithms/time_evolvers/variational/__init__.py delete mode 100644 qiskit/algorithms/time_evolvers/variational/solvers/__init__.py delete mode 100644 qiskit/algorithms/time_evolvers/variational/solvers/ode/__init__.py delete mode 100644 qiskit/algorithms/time_evolvers/variational/solvers/ode/abstract_ode_function.py delete mode 100644 qiskit/algorithms/time_evolvers/variational/solvers/ode/forward_euler_solver.py delete mode 100644 qiskit/algorithms/time_evolvers/variational/solvers/ode/ode_function.py delete mode 100644 qiskit/algorithms/time_evolvers/variational/solvers/ode/ode_function_factory.py delete mode 100644 qiskit/algorithms/time_evolvers/variational/solvers/ode/var_qte_ode_solver.py delete mode 100644 qiskit/algorithms/time_evolvers/variational/solvers/var_qte_linear_solver.py delete mode 100644 qiskit/algorithms/time_evolvers/variational/var_qite.py delete mode 100644 qiskit/algorithms/time_evolvers/variational/var_qrte.py delete mode 100644 qiskit/algorithms/time_evolvers/variational/var_qte.py delete mode 100644 qiskit/algorithms/time_evolvers/variational/var_qte_result.py delete mode 100644 qiskit/algorithms/time_evolvers/variational/variational_principles/__init__.py delete mode 100644 qiskit/algorithms/time_evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py delete mode 100644 qiskit/algorithms/time_evolvers/variational/variational_principles/imaginary_variational_principle.py delete mode 100644 qiskit/algorithms/time_evolvers/variational/variational_principles/real_mc_lachlan_principle.py delete mode 100644 qiskit/algorithms/time_evolvers/variational/variational_principles/real_variational_principle.py delete mode 100644 qiskit/algorithms/time_evolvers/variational/variational_principles/variational_principle.py delete mode 100644 qiskit/algorithms/utils/__init__.py delete mode 100644 qiskit/algorithms/utils/set_batching.py delete mode 100644 qiskit/algorithms/utils/validate_bounds.py delete mode 100644 qiskit/algorithms/utils/validate_initial_point.py delete mode 100644 qiskit/algorithms/variational_algorithm.py delete mode 100644 test/python/algorithms/__init__.py delete mode 100644 test/python/algorithms/algorithms_test_case.py delete mode 100644 test/python/algorithms/eigensolvers/__init__.py delete mode 100644 test/python/algorithms/eigensolvers/test_numpy_eigensolver.py delete mode 100644 test/python/algorithms/eigensolvers/test_vqd.py delete mode 100644 test/python/algorithms/evolvers/__init__.py delete mode 100644 test/python/algorithms/evolvers/test_evolution_problem.py delete mode 100644 test/python/algorithms/evolvers/test_evolution_result.py delete mode 100644 test/python/algorithms/evolvers/trotterization/__init__.py delete mode 100644 test/python/algorithms/evolvers/trotterization/test_trotter_qrte.py delete mode 100644 test/python/algorithms/gradients/__init__.py delete mode 100644 test/python/algorithms/gradients/logging_primitives.py delete mode 100644 test/python/algorithms/gradients/test_estimator_gradient.py delete mode 100644 test/python/algorithms/gradients/test_qfi.py delete mode 100644 test/python/algorithms/gradients/test_qgt.py delete mode 100644 test/python/algorithms/gradients/test_sampler_gradient.py delete mode 100644 test/python/algorithms/minimum_eigensolvers/__init__.py delete mode 100644 test/python/algorithms/minimum_eigensolvers/test_adapt_vqe.py delete mode 100644 test/python/algorithms/minimum_eigensolvers/test_numpy_minimum_eigensolver.py delete mode 100644 test/python/algorithms/minimum_eigensolvers/test_qaoa.py delete mode 100644 test/python/algorithms/minimum_eigensolvers/test_qaoa_opflow.py delete mode 100644 test/python/algorithms/minimum_eigensolvers/test_sampling_vqe.py delete mode 100644 test/python/algorithms/minimum_eigensolvers/test_vqe.py delete mode 100644 test/python/algorithms/optimizers/__init__.py delete mode 100644 test/python/algorithms/optimizers/test_gradient_descent.py delete mode 100644 test/python/algorithms/optimizers/test_optimizer_aqgd.py delete mode 100644 test/python/algorithms/optimizers/test_optimizer_nft.py delete mode 100644 test/python/algorithms/optimizers/test_optimizers.py delete mode 100644 test/python/algorithms/optimizers/test_optimizers_scikitquant.py delete mode 100644 test/python/algorithms/optimizers/test_spsa.py delete mode 100644 test/python/algorithms/optimizers/test_umda.py delete mode 100644 test/python/algorithms/optimizers/utils/__init__.py delete mode 100644 test/python/algorithms/optimizers/utils/test_learning_rate.py delete mode 100644 test/python/algorithms/state_fidelities/__init__.py delete mode 100644 test/python/algorithms/state_fidelities/test_compute_uncompute.py delete mode 100644 test/python/algorithms/test_amplitude_estimators.py delete mode 100644 test/python/algorithms/test_aux_ops_evaluator.py delete mode 100644 test/python/algorithms/test_backendv1.py delete mode 100644 test/python/algorithms/test_backendv2.py delete mode 100644 test/python/algorithms/test_entangler_map.py delete mode 100644 test/python/algorithms/test_grover.py delete mode 100644 test/python/algorithms/test_measure_error_mitigation.py delete mode 100644 test/python/algorithms/test_numpy_eigen_solver.py delete mode 100644 test/python/algorithms/test_numpy_minimum_eigen_solver.py delete mode 100644 test/python/algorithms/test_observables_evaluator.py delete mode 100644 test/python/algorithms/test_phase_estimator.py delete mode 100644 test/python/algorithms/test_qaoa.py delete mode 100644 test/python/algorithms/test_skip_qobj_validation.py delete mode 100644 test/python/algorithms/test_validation.py delete mode 100644 test/python/algorithms/test_vqd.py delete mode 100644 test/python/algorithms/test_vqe.py delete mode 100644 test/python/algorithms/time_evolvers/__init__.py delete mode 100644 test/python/algorithms/time_evolvers/classical_methods/__init__.py delete mode 100644 test/python/algorithms/time_evolvers/classical_methods/test_scipy_imaginary_evolver.py delete mode 100644 test/python/algorithms/time_evolvers/classical_methods/test_scipy_real_evolver.py delete mode 100644 test/python/algorithms/time_evolvers/test_pvqd.py delete mode 100644 test/python/algorithms/time_evolvers/test_time_evolution_problem.py delete mode 100644 test/python/algorithms/time_evolvers/test_time_evolution_result.py delete mode 100644 test/python/algorithms/time_evolvers/test_trotter_qrte.py delete mode 100644 test/python/algorithms/time_evolvers/variational/__init__.py delete mode 100644 test/python/algorithms/time_evolvers/variational/solvers/__init__.py delete mode 100644 test/python/algorithms/time_evolvers/variational/solvers/expected_results/__init__.py delete mode 100644 test/python/algorithms/time_evolvers/variational/solvers/expected_results/test_varqte_linear_solver_expected_1.py delete mode 100644 test/python/algorithms/time_evolvers/variational/solvers/ode/__init__.py delete mode 100644 test/python/algorithms/time_evolvers/variational/solvers/ode/test_forward_euler_solver.py delete mode 100644 test/python/algorithms/time_evolvers/variational/solvers/ode/test_ode_function.py delete mode 100644 test/python/algorithms/time_evolvers/variational/solvers/ode/test_var_qte_ode_solver.py delete mode 100644 test/python/algorithms/time_evolvers/variational/solvers/test_varqte_linear_solver.py delete mode 100644 test/python/algorithms/time_evolvers/variational/test_var_qite.py delete mode 100644 test/python/algorithms/time_evolvers/variational/test_var_qrte.py delete mode 100644 test/python/algorithms/time_evolvers/variational/test_var_qte.py delete mode 100644 test/python/algorithms/time_evolvers/variational/variational_principles/__init__.py delete mode 100644 test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/__init__.py delete mode 100644 test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected1.py delete mode 100644 test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected2.py delete mode 100644 test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected3.py delete mode 100644 test/python/algorithms/time_evolvers/variational/variational_principles/imaginary/__init__.py delete mode 100644 test/python/algorithms/time_evolvers/variational/variational_principles/imaginary/test_imaginary_mc_lachlan_principle.py delete mode 100644 test/python/algorithms/time_evolvers/variational/variational_principles/real/__init__.py delete mode 100644 test/python/algorithms/time_evolvers/variational/variational_principles/real/test_real_mc_lachlan_principle.py delete mode 100644 test/python/algorithms/utils/__init__.py delete mode 100644 test/python/algorithms/utils/test_validate_bounds.py delete mode 100644 test/python/algorithms/utils/test_validate_initial_point.py diff --git a/qiskit/algorithms/__init__.py b/qiskit/algorithms/__init__.py deleted file mode 100644 index 18767f1fdd01..000000000000 --- a/qiskit/algorithms/__init__.py +++ /dev/null @@ -1,430 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -===================================== -Algorithms (:mod:`qiskit.algorithms`) -===================================== - -.. deprecated:: 0.25.0 - - The :mod:`qiskit.algorithms` module has been migrated to an independent package: - https://github.com/qiskit-community/qiskit-algorithms. - The current import path is deprecated and will be removed no earlier - than 3 months after the release date. If your code uses primitives, you can run - ``pip install qiskit_algorithms`` and import ``from qiskit_algorithms`` instead. - If you use opflow/quantum instance-based algorithms, please update your code to - use primitives following: https://qisk.it/algo_migration before migrating to - the new package. - -It contains a collection of quantum algorithms, for use with quantum computers, to -carry out research and investigate how to solve problems in different domains on -near-term quantum devices with short depth circuits. - -Algorithms configuration includes the use of :mod:`~qiskit.algorithms.optimizers` which -were designed to be swappable sub-parts of an algorithm. Any component and may be exchanged for -a different implementation of the same component type in order to potentially alter the behavior -and outcome of the algorithm. - -Quantum algorithms are run via a :class:`~qiskit.algorithms.QuantumInstance` -which must be set with the -desired backend where the algorithm's circuits will be executed and be configured with a number of -compile and runtime parameters controlling circuit compilation and execution. It ultimately uses -`Terra `__ for the actual compilation and execution of the quantum -circuits created by the algorithm and its components. - -.. currentmodule:: qiskit.algorithms - -Algorithms -========== - -It contains a variety of quantum algorithms and these have been grouped by logical function such -as minimum eigensolvers and amplitude amplifiers. - - -Amplitude Amplifiers --------------------- - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - AmplificationProblem - AmplitudeAmplifier - Grover - GroverResult - - -Amplitude Estimators --------------------- - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - AmplitudeEstimator - AmplitudeEstimatorResult - AmplitudeEstimation - AmplitudeEstimationResult - EstimationProblem - FasterAmplitudeEstimation - FasterAmplitudeEstimationResult - IterativeAmplitudeEstimation - IterativeAmplitudeEstimationResult - MaximumLikelihoodAmplitudeEstimation - MaximumLikelihoodAmplitudeEstimationResult - - -Eigensolvers ------------- - -Algorithms to find eigenvalues of an operator. For chemistry these can be used to find excited -states of a molecule, and ``qiskit-nature`` has some algorithms that leverage chemistry specific -knowledge to do this in that application domain. - -Primitive-based Eigensolvers -++++++++++++++++++++++++++++ - -These algorithms are based on the Qiskit Primitives, a new execution paradigm that replaces the use -of :class:`.QuantumInstance` in algorithms. To ensure continued support and development, we recommend -using the primitive-based Eigensolvers in place of the legacy :class:`.QuantumInstance`-based ones. - -.. autosummary:: - :toctree: ../stubs/ - - eigensolvers - - -Legacy Eigensolvers -+++++++++++++++++++ - -These algorithms, still based on the :class:`.QuantumInstance`, are superseded -by the primitive-based versions in the section above but are still supported for now. - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - Eigensolver - EigensolverResult - NumPyEigensolver - VQD - VQDResult - - -Time Evolvers -------------- - -Algorithms to evolve quantum states in time. Both real and imaginary time evolution is possible -with algorithms that support them. For machine learning, Quantum Imaginary Time Evolution might be -used to train Quantum Boltzmann Machine Neural Networks for example. - -Primitive-based Time Evolvers -+++++++++++++++++++++++++++++ - -These algorithms are based on the Qiskit Primitives, a new execution paradigm that replaces the use -of :class:`.QuantumInstance` in algorithms. To ensure continued support and development, we recommend -using the primitive-based Time Evolvers in place of the legacy :class:`.QuantumInstance`-based ones. - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - RealTimeEvolver - ImaginaryTimeEvolver - TimeEvolutionResult - TimeEvolutionProblem - PVQD - PVQDResult - SciPyImaginaryEvolver - SciPyRealEvolver - VarQITE - VarQRTE - -Legacy Time Evolvers -++++++++++++++++++++ - -These algorithms, still based on the :class:`.QuantumInstance`, are superseded -by the primitive-based versions in the section above but are still supported for now. - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - RealEvolver - ImaginaryEvolver - TrotterQRTE - EvolutionResult - EvolutionProblem - - -Variational Quantum Time Evolution -++++++++++++++++++++++++++++++++++ - -Classes used by variational quantum time evolution algorithms - :class:`.VarQITE` and -:class:`.VarQRTE`. - -.. autosummary:: - :toctree: ../stubs/ - - time_evolvers.variational - - -Trotterization-based Quantum Real Time Evolution -++++++++++++++++++++++++++++++++++++++++++++++++ - -Package for primitives-enabled Trotterization-based quantum time evolution -algorithm - :class:`~.time_evolvers.TrotterQRTE`. - -.. autosummary:: - :toctree: ../stubs/ - - time_evolvers.trotterization - - -Gradients ----------- - -Algorithms to calculate the gradient of a quantum circuit. - -.. autosummary:: - :toctree: ../stubs/ - - gradients - - -Minimum Eigensolvers ---------------------- - -Algorithms that can find the minimum eigenvalue of an operator. - -Primitive-based Minimum Eigensolvers -++++++++++++++++++++++++++++++++++++ - -These algorithms are based on the Qiskit Primitives, a new execution paradigm that replaces the use -of :class:`.QuantumInstance` in algorithms. To ensure continued support and development, we recommend -using the primitive-based Minimum Eigensolvers in place of the legacy :class:`.QuantumInstance`-based -ones. - -.. autosummary:: - :toctree: ../stubs/ - - minimum_eigensolvers - - -Legacy Minimum Eigensolvers -+++++++++++++++++++++++++++ - -These algorithms, still based on the :class:`.QuantumInstance`, are superseded -by the primitive-based versions in the section above but are still supported for now. - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - MinimumEigensolver - MinimumEigensolverResult - NumPyMinimumEigensolver - QAOA - VQE - - -Optimizers ----------- - -Classical optimizers for use by quantum variational algorithms. - -.. autosummary:: - :toctree: ../stubs/ - - optimizers - - -Phase Estimators ----------------- - -Algorithms that estimate the phases of eigenstates of a unitary. - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - HamiltonianPhaseEstimation - HamiltonianPhaseEstimationResult - PhaseEstimationScale - PhaseEstimation - PhaseEstimationResult - IterativePhaseEstimation - - -State Fidelities ----------------- - -Algorithms that compute the fidelity of pairs of quantum states. - -.. autosummary:: - :toctree: ../stubs/ - - state_fidelities - - -Exceptions ----------- - -.. autoexception:: AlgorithmError - -Utility classes ---------------- - -Utility classes used by algorithms (mainly for type-hinting purposes). - -.. autosummary:: - :toctree: ../stubs/ - - AlgorithmJob - -Utility functions ------------------ - -Utility functions used by algorithms. - -.. autofunction:: eval_observables -.. autofunction:: estimate_observables - -""" -import warnings - -from .algorithm_job import AlgorithmJob -from .algorithm_result import AlgorithmResult -from .evolvers import EvolutionResult, EvolutionProblem -from .evolvers.real_evolver import RealEvolver -from .evolvers.imaginary_evolver import ImaginaryEvolver -from .variational_algorithm import VariationalAlgorithm, VariationalResult -from .amplitude_amplifiers import Grover, GroverResult, AmplificationProblem, AmplitudeAmplifier -from .amplitude_estimators import ( - AmplitudeEstimator, - AmplitudeEstimatorResult, - AmplitudeEstimation, - AmplitudeEstimationResult, - FasterAmplitudeEstimation, - FasterAmplitudeEstimationResult, - IterativeAmplitudeEstimation, - IterativeAmplitudeEstimationResult, - MaximumLikelihoodAmplitudeEstimation, - MaximumLikelihoodAmplitudeEstimationResult, - EstimationProblem, -) -from .eigen_solvers import NumPyEigensolver, Eigensolver, EigensolverResult, VQD, VQDResult -from .minimum_eigen_solvers import ( - VQE, - VQEResult, - QAOA, - NumPyMinimumEigensolver, - MinimumEigensolver, - MinimumEigensolverResult, -) -from .phase_estimators import ( - HamiltonianPhaseEstimation, - HamiltonianPhaseEstimationResult, - PhaseEstimationScale, - PhaseEstimation, - PhaseEstimationResult, - IterativePhaseEstimation, -) -from .exceptions import AlgorithmError -from .aux_ops_evaluator import eval_observables -from .observables_evaluator import estimate_observables -from .evolvers.trotterization import TrotterQRTE - -from .time_evolvers import ( - ImaginaryTimeEvolver, - RealTimeEvolver, - TimeEvolutionProblem, - TimeEvolutionResult, - PVQD, - PVQDResult, - SciPyImaginaryEvolver, - SciPyRealEvolver, - VarQITE, - VarQRTE, - VarQTE, - VarQTEResult, -) - -__all__ = [ - "AlgorithmJob", - "AlgorithmResult", - "VariationalAlgorithm", - "VariationalResult", - "AmplitudeAmplifier", - "AmplificationProblem", - "Grover", - "GroverResult", - "AmplitudeEstimator", - "AmplitudeEstimatorResult", - "AmplitudeEstimation", - "AmplitudeEstimationResult", - "FasterAmplitudeEstimation", - "FasterAmplitudeEstimationResult", - "IterativeAmplitudeEstimation", - "IterativeAmplitudeEstimationResult", - "MaximumLikelihoodAmplitudeEstimation", - "MaximumLikelihoodAmplitudeEstimationResult", - "EstimationProblem", - "NumPyEigensolver", - "RealEvolver", - "ImaginaryEvolver", - "RealTimeEvolver", - "ImaginaryTimeEvolver", - "TrotterQRTE", - "EvolutionResult", - "EvolutionProblem", - "TimeEvolutionResult", - "TimeEvolutionProblem", - "Eigensolver", - "EigensolverResult", - "VQE", - "VQEResult", - "QAOA", - "NumPyMinimumEigensolver", - "MinimumEigensolver", - "MinimumEigensolverResult", - "HamiltonianPhaseEstimation", - "HamiltonianPhaseEstimationResult", - "VQD", - "VQDResult", - "PhaseEstimationScale", - "PhaseEstimation", - "PhaseEstimationResult", - "PVQD", - "PVQDResult", - "SciPyRealEvolver", - "SciPyImaginaryEvolver", - "IterativePhaseEstimation", - "AlgorithmError", - "eval_observables", - "estimate_observables", - "VarQITE", - "VarQRTE", - "VarQTE", - "VarQTEResult", -] - -warnings.warn( - "``qiskit.algorithms`` has been migrated to an independent package: " - "https://github.com/qiskit-community/qiskit-algorithms. " - "The ``qiskit.algorithms`` import path is deprecated as of qiskit-terra 0.25.0 and " - "will be removed no earlier than 3 months after the release date. " - "Please run ``pip install qiskit_algorithms`` and use ``import qiskit_algorithms`` instead.", - category=DeprecationWarning, - stacklevel=2, -) diff --git a/qiskit/algorithms/algorithm_job.py b/qiskit/algorithms/algorithm_job.py deleted file mode 100644 index 16db4df93dfc..000000000000 --- a/qiskit/algorithms/algorithm_job.py +++ /dev/null @@ -1,24 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -AlgorithmJob class -""" -from qiskit.primitives.primitive_job import PrimitiveJob - - -class AlgorithmJob(PrimitiveJob): - """ - This empty class is introduced for typing purposes. - """ - - pass diff --git a/qiskit/algorithms/algorithm_result.py b/qiskit/algorithms/algorithm_result.py deleted file mode 100644 index 0804303a4ef6..000000000000 --- a/qiskit/algorithms/algorithm_result.py +++ /dev/null @@ -1,65 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -This module implements the abstract base class for algorithm results. -""" - -from abc import ABC -import inspect -import pprint - - -class AlgorithmResult(ABC): - """Abstract Base Class for algorithm results.""" - - def __str__(self) -> str: - result = {} - for name, value in inspect.getmembers(self): - if ( - not name.startswith("_") - and not inspect.ismethod(value) - and not inspect.isfunction(value) - and hasattr(self, name) - ): - - result[name] = value - - return pprint.pformat(result, indent=4) - - def combine(self, result: "AlgorithmResult") -> None: - """ - Any property from the argument that exists in the receiver is - updated. - Args: - result: Argument result with properties to be set. - Raises: - TypeError: Argument is None - """ - if result is None: - raise TypeError("Argument result expected.") - if result == self: - return - - # find any result public property that exists in the receiver - for name, value in inspect.getmembers(result): - if ( - not name.startswith("_") - and not inspect.ismethod(value) - and not inspect.isfunction(value) - and hasattr(self, name) - ): - try: - setattr(self, name, value) - except AttributeError: - # some attributes may be read only - pass diff --git a/qiskit/algorithms/amplitude_amplifiers/__init__.py b/qiskit/algorithms/amplitude_amplifiers/__init__.py deleted file mode 100644 index bc45f18106bd..000000000000 --- a/qiskit/algorithms/amplitude_amplifiers/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Amplitude Amplifiers Package""" - -from .amplitude_amplifier import AmplitudeAmplifier, AmplitudeAmplifierResult -from .amplification_problem import AmplificationProblem -from .grover import Grover, GroverResult - -__all__ = [ - "AmplitudeAmplifier", - "AmplitudeAmplifierResult", - "AmplificationProblem", - "Grover", - "GroverResult", -] diff --git a/qiskit/algorithms/amplitude_amplifiers/amplification_problem.py b/qiskit/algorithms/amplitude_amplifiers/amplification_problem.py deleted file mode 100644 index 67b20751c417..000000000000 --- a/qiskit/algorithms/amplitude_amplifiers/amplification_problem.py +++ /dev/null @@ -1,213 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Amplification problem class.""" -from __future__ import annotations - -from collections.abc import Callable -from typing import Any - -from qiskit.circuit import QuantumCircuit -from qiskit.circuit.library import GroverOperator -from qiskit.quantum_info import Statevector - - -class AmplificationProblem: - """The amplification problem is the input to amplitude amplification algorithms, like Grover. - - This class contains all problem-specific information required to run an amplitude amplification - algorithm. It minimally contains the Grover operator. It can further hold some post processing - on the optimal bitstring. - """ - - def __init__( - self, - oracle: QuantumCircuit | Statevector, - state_preparation: QuantumCircuit | None = None, - grover_operator: QuantumCircuit | None = None, - post_processing: Callable[[str], Any] | None = None, - objective_qubits: int | list[int] | None = None, - is_good_state: Callable[[str], bool] | list[int] | list[str] | Statevector | None = None, - ) -> None: - r""" - Args: - oracle: The oracle reflecting about the bad states. - state_preparation: A circuit preparing the input state, referred to as - :math:`\mathcal{A}`. If None, a layer of Hadamard gates is used. - grover_operator: The Grover operator :math:`\mathcal{Q}` used as unitary in the - phase estimation circuit. If None, this operator is constructed from the ``oracle`` - and ``state_preparation``. - post_processing: A mapping applied to the most likely bitstring. - objective_qubits: If set, specifies the indices of the qubits that should be measured. - If None, all qubits will be measured. The ``is_good_state`` function will be - applied on the measurement outcome of these qubits. - is_good_state: A function to check whether a string represents a good state. By default - if the ``oracle`` argument has an ``evaluate_bitstring`` method (currently only - provided by the :class:`~qiskit.circuit.library.PhaseOracle` class) this will be - used, otherwise this kwarg is required and **must** be specified. - """ - self._oracle = oracle - self._state_preparation = state_preparation - self._grover_operator = grover_operator - self._post_processing = post_processing - self._objective_qubits = objective_qubits - if is_good_state is not None: - self._is_good_state = is_good_state - elif hasattr(oracle, "evaluate_bitstring"): - self._is_good_state = oracle.evaluate_bitstring - else: - self._is_good_state = None - - @property - def oracle(self) -> QuantumCircuit | Statevector: - """Return the oracle. - - Returns: - The oracle. - """ - return self._oracle - - @oracle.setter - def oracle(self, oracle: QuantumCircuit | Statevector) -> None: - """Set the oracle. - - Args: - oracle: The oracle. - """ - self._oracle = oracle - - @property - def state_preparation(self) -> QuantumCircuit: - r"""Get the state preparation operator :math:`\mathcal{A}`. - - Returns: - The :math:`\mathcal{A}` operator as `QuantumCircuit`. - """ - if self._state_preparation is None: - state_preparation = QuantumCircuit(self.oracle.num_qubits) - state_preparation.h(state_preparation.qubits) - return state_preparation - - return self._state_preparation - - @state_preparation.setter - def state_preparation(self, state_preparation: QuantumCircuit | None) -> None: - r"""Set the :math:`\mathcal{A}` operator. If None, a layer of Hadamard gates is used. - - Args: - state_preparation: The new :math:`\mathcal{A}` operator or None. - """ - self._state_preparation = state_preparation - - @property - def post_processing(self) -> Callable[[str], Any]: - """Apply post processing to the input value. - - Returns: - A handle to the post processing function. Acts as identity by default. - """ - if self._post_processing is None: - return lambda x: x - - return self._post_processing - - @post_processing.setter - def post_processing(self, post_processing: Callable[[str], Any]) -> None: - """Set the post processing function. - - Args: - post_processing: A handle to the post processing function. - """ - self._post_processing = post_processing - - @property - def objective_qubits(self) -> list[int]: - """The indices of the objective qubits. - - Returns: - The indices of the objective qubits as list of integers. - """ - if self._objective_qubits is None: - return list(range(self.oracle.num_qubits)) - - if isinstance(self._objective_qubits, int): - return [self._objective_qubits] - - return self._objective_qubits - - @objective_qubits.setter - def objective_qubits(self, objective_qubits: int | list[int] | None) -> None: - """Set the objective qubits. - - Args: - objective_qubits: The indices of the qubits that should be measured. - If None, all qubits will be measured. The ``is_good_state`` function will be - applied on the measurement outcome of these qubits. - """ - self._objective_qubits = objective_qubits - - @property - def is_good_state(self) -> Callable[[str], bool]: - """Check whether a provided bitstring is a good state or not. - - Returns: - A callable that takes in a bitstring and returns True if the measurement is a good - state, False otherwise. - """ - if (self._is_good_state is None) or callable(self._is_good_state): - return self._is_good_state # returns None if no is_good_state arg has been set - elif isinstance(self._is_good_state, list): - if all(isinstance(good_bitstr, str) for good_bitstr in self._is_good_state): - return lambda bitstr: bitstr in self._is_good_state - else: - return lambda bitstr: all( - bitstr[good_index] == "1" for good_index in self._is_good_state - ) - - return lambda bitstr: bitstr in self._is_good_state.probabilities_dict() - - @is_good_state.setter - def is_good_state( - self, is_good_state: Callable[[str], bool] | list[int] | list[str] | Statevector - ) -> None: - """Set the ``is_good_state`` function. - - Args: - is_good_state: A function to determine whether a bitstring represents a good state. - """ - self._is_good_state = is_good_state - - @property - def grover_operator(self) -> QuantumCircuit | None: - r"""Get the :math:`\mathcal{Q}` operator, or Grover operator. - - If the Grover operator is not set, we try to build it from the :math:`\mathcal{A}` operator - and `objective_qubits`. This only works if `objective_qubits` is a list of integers. - - Returns: - The Grover operator, or None if neither the Grover operator nor the - :math:`\mathcal{A}` operator is set. - """ - if self._grover_operator is None: - return GroverOperator(self.oracle, self.state_preparation) - return self._grover_operator - - @grover_operator.setter - def grover_operator(self, grover_operator: QuantumCircuit | None) -> None: - r"""Set the :math:`\mathcal{Q}` operator. - - If None, this operator is constructed from the ``oracle`` and ``state_preparation``. - - Args: - grover_operator: The new :math:`\mathcal{Q}` operator or None. - """ - self._grover_operator = grover_operator diff --git a/qiskit/algorithms/amplitude_amplifiers/amplitude_amplifier.py b/qiskit/algorithms/amplitude_amplifiers/amplitude_amplifier.py deleted file mode 100644 index 33ef90cb624e..000000000000 --- a/qiskit/algorithms/amplitude_amplifiers/amplitude_amplifier.py +++ /dev/null @@ -1,127 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The interface for amplification algorithms and results.""" -from __future__ import annotations - -from abc import ABC, abstractmethod -from typing import Any - -import numpy as np - -from .amplification_problem import AmplificationProblem -from ..algorithm_result import AlgorithmResult - - -class AmplitudeAmplifier(ABC): - """The interface for amplification algorithms.""" - - @abstractmethod - def amplify(self, amplification_problem: AmplificationProblem) -> "AmplitudeAmplifierResult": - """Run the amplification algorithm. - - Args: - amplification_problem: The amplification problem. - - Returns: - The result as a ``AmplificationResult``, where e.g. the most likely state can be queried - as ``result.top_measurement``. - """ - raise NotImplementedError - - -class AmplitudeAmplifierResult(AlgorithmResult): - """The amplification result base class.""" - - def __init__(self) -> None: - super().__init__() - self._top_measurement: str | None = None - self._assignment = None - self._oracle_evaluation: bool | None = None - self._circuit_results: list[np.ndarray] | list[dict[str, int]] | None = None - self._max_probability: float | None = None - - @property - def top_measurement(self) -> str | None: - """The most frequently measured output as bitstring. - - Returns: - The most frequently measured output state. - """ - return self._top_measurement - - @top_measurement.setter - def top_measurement(self, value: str) -> None: - """Set the most frequently measured bitstring. - - Args: - value: A new value for the top measurement. - """ - self._top_measurement = value - - @property - def assignment(self) -> Any: - """The post-processed value of the most likely bitstring. - - Returns: - The output of the ``post_processing`` function of the respective - ``AmplificationProblem``, where the input is the ``top_measurement``. The type - is the same as the return type of the post-processing function. - """ - return self._assignment - - @assignment.setter - def assignment(self, value: Any) -> None: - """Set the value for the assignment. - - Args: - value: A new value for the assignment/solution. - """ - self._assignment = value - - @property - def oracle_evaluation(self) -> bool: - """Whether the classical oracle evaluation of the top measurement was True or False. - - Returns: - The classical oracle evaluation of the top measurement. - """ - return self._oracle_evaluation - - @oracle_evaluation.setter - def oracle_evaluation(self, value: bool) -> None: - """Set the classical oracle evaluation of the top measurement. - - Args: - value: A new value for the classical oracle evaluation. - """ - self._oracle_evaluation = value - - @property - def circuit_results(self) -> list[np.ndarray] | list[dict[str, int]] | None: - """Return the circuit results. Can be a statevector or counts dictionary.""" - return self._circuit_results - - @circuit_results.setter - def circuit_results(self, value: list[np.ndarray] | list[dict[str, int]]) -> None: - """Set the circuit results.""" - self._circuit_results = value - - @property - def max_probability(self) -> float: - """Return the maximum sampling probability.""" - return self._max_probability - - @max_probability.setter - def max_probability(self, value: float) -> None: - """Set the maximum sampling probability.""" - self._max_probability = value diff --git a/qiskit/algorithms/amplitude_amplifiers/grover.py b/qiskit/algorithms/amplitude_amplifiers/grover.py deleted file mode 100644 index be04929afa03..000000000000 --- a/qiskit/algorithms/amplitude_amplifiers/grover.py +++ /dev/null @@ -1,449 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Grover's search algorithm.""" -from __future__ import annotations - -import itertools -import operator -import warnings -from collections.abc import Iterator, Generator -from typing import Any - -import numpy as np - -from qiskit import ClassicalRegister, QuantumCircuit -from qiskit.algorithms.exceptions import AlgorithmError -from qiskit.primitives import BaseSampler -from qiskit.providers import Backend -from qiskit.quantum_info import partial_trace, Statevector -from qiskit.utils import QuantumInstance, algorithm_globals -from qiskit.utils.deprecation import deprecate_arg, deprecate_func - -from .amplification_problem import AmplificationProblem -from .amplitude_amplifier import AmplitudeAmplifier, AmplitudeAmplifierResult - - -class Grover(AmplitudeAmplifier): - r"""Grover's Search algorithm. - - .. note:: - - If you want to learn more about the theory behind Grover's Search algorithm, check - out the `Qiskit Textbook `_. - or the `Qiskit Tutorials - `_ - for more concrete how-to examples. - - Grover's Search [1, 2] is a well known quantum algorithm that can be used for - searching through unstructured collections of records for particular targets - with quadratic speedup compared to classical algorithms. - - Given a set :math:`X` of :math:`N` elements :math:`X=\{x_1,x_2,\ldots,x_N\}` - and a boolean function :math:`f : X \rightarrow \{0,1\}`, the goal of an - unstructured-search problem is to find an element :math:`x^* \in X` such - that :math:`f(x^*)=1`. - - The search is called *unstructured* because there are no guarantees as to how - the database is ordered. On a sorted database, for instance, one could perform - binary search to find an element in :math:`\mathbb{O}(\log N)` worst-case time. - Instead, in an unstructured-search problem, there is no prior knowledge about - the contents of the database. With classical circuits, there is no alternative - but to perform a linear number of queries to find the target element. - Conversely, Grover's Search algorithm allows to solve the unstructured-search - problem on a quantum computer in :math:`\mathcal{O}(\sqrt{N})` queries. - - To carry out this search a so-called oracle is required, that flags a good element/state. - The action of the oracle :math:`\mathcal{S}_f` is - - .. math:: - - \mathcal{S}_f |x\rangle = (-1)^{f(x)} |x\rangle, - - i.e. it flips the phase of the state :math:`|x\rangle` if :math:`x` is a hit. - The details of how :math:`S_f` works are unimportant to the algorithm; Grover's - search algorithm treats the oracle as a black box. - - This class supports oracles in form of a :class:`~qiskit.circuit.QuantumCircuit`. - - With the given oracle, Grover's Search constructs the Grover operator to amplify the - amplitudes of the good states: - - .. math:: - - \mathcal{Q} = H^{\otimes n} \mathcal{S}_0 H^{\otimes n} \mathcal{S}_f - = D \mathcal{S}_f, - - where :math:`\mathcal{S}_0` flips the phase of the all-zero state and acts as identity - on all other states. Sometimes the first three operands are summarized as diffusion operator, - which implements a reflection over the equal superposition state. - - If the number of solutions is known, we can calculate how often :math:`\mathcal{Q}` should be - applied to find a solution with very high probability, see the method - `optimal_num_iterations`. If the number of solutions is unknown, the algorithm tries different - powers of Grover's operator, see the `iterations` argument, and after each iteration checks - if a good state has been measured using `good_state`. - - The generalization of Grover's Search, Quantum Amplitude Amplification [3], uses a modified - version of :math:`\mathcal{Q}` where the diffusion operator does not reflect about the - equal superposition state, but another state specified via an operator :math:`\mathcal{A}`: - - .. math:: - - \mathcal{Q} = \mathcal{A} \mathcal{S}_0 \mathcal{A}^\dagger \mathcal{S}_f. - - For more information, see the :class:`~qiskit.circuit.library.GroverOperator` in the - circuit library. - - References: - [1]: L. K. Grover (1996), A fast quantum mechanical algorithm for database search, - `arXiv:quant-ph/9605043 `_. - [2]: I. Chuang & M. Nielsen, Quantum Computation and Quantum Information, - Cambridge: Cambridge University Press, 2000. Chapter 6.1.2. - [3]: Brassard, G., Hoyer, P., Mosca, M., & Tapp, A. (2000). - Quantum Amplitude Amplification and Estimation. - `arXiv:quant-ph/0005055 `_. - """ - - @deprecate_arg( - "quantum_instance", - additional_msg=( - "Instead, use the ``sampler`` argument. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - iterations: list[int] | Iterator[int] | int | None = None, - growth_rate: float | None = None, - sample_from_iterations: bool = False, - quantum_instance: QuantumInstance | Backend | None = None, - sampler: BaseSampler | None = None, - ) -> None: - r""" - Args: - iterations: Specify the number of iterations/power of Grover's operator to be checked. - * If an int, only one circuit is run with that power of the Grover operator. - If the number of solutions is known, this option should be used with the optimal - power. The optimal power can be computed with ``Grover.optimal_num_iterations``. - * If a list, all the powers in the list are run in the specified order. - * If an iterator, the powers yielded by the iterator are checked, until a maximum - number of iterations or maximum power is reached. - * If ``None``, the :obj:`AmplificationProblem` provided must have an ``is_good_state``, - and circuits are run until that good state is reached. - growth_rate: If specified, the iterator is set to increasing powers of ``growth_rate``, - i.e. to ``int(growth_rate ** 1), int(growth_rate ** 2), ...`` until a maximum - number of iterations is reached. - sample_from_iterations: If True, instead of taking the values in ``iterations`` as - powers of the Grover operator, a random integer sample between 0 and smaller value - than the iteration is used as a power, see [1], Section 4. - quantum_instance: Deprecated: A Quantum Instance or Backend to run the circuits. - sampler: A Sampler to use for sampling the results of the circuits. - - Raises: - ValueError: If ``growth_rate`` is a float but not larger than 1. - ValueError: If both ``iterations`` and ``growth_rate`` is set. - - References: - [1]: Boyer et al., Tight bounds on quantum searching - ``_ - """ - # set default value - if growth_rate is None and iterations is None: - growth_rate = 1.2 - - if growth_rate is not None and iterations is not None: - raise ValueError("Pass either a value for iterations or growth_rate, not both.") - - if growth_rate is not None: - # yield iterations ** 1, iterations ** 2, etc. and casts to int - self._iterations: Generator[int, None, None] | list[int] = ( - int(growth_rate**x) for x in itertools.count(1) - ) - elif isinstance(iterations, int): - self._iterations = [iterations] - else: - self._iterations = iterations - - if quantum_instance is not None and sampler is not None: - raise ValueError("Only one of quantum_instance or sampler can be passed, not both!") - - # check positionally passing the sampler in the place of quantum_instance - # which will be removed in future - if isinstance(quantum_instance, BaseSampler): - sampler = quantum_instance - quantum_instance = None - - self._quantum_instance: QuantumInstance | None = None - if quantum_instance is not None: - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - self.quantum_instance = quantum_instance - - self._sampler = sampler - - self._sample_from_iterations = sample_from_iterations - self._iterations_arg = iterations - - @property - @deprecate_func( - since="0.24.0", - is_property=True, - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - ) - def quantum_instance(self) -> QuantumInstance | None: - r"""Deprecated. Get the quantum instance. - - Returns: - The quantum instance used to run this algorithm. - """ - return self._quantum_instance - - @quantum_instance.setter - @deprecate_func( - since="0.24.0", - is_property=True, - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - ) - def quantum_instance(self, quantum_instance: QuantumInstance | Backend) -> None: - r"""Deprecated. Set quantum instance. - - Args: - quantum_instance: The quantum instance used to run this algorithm. - """ - if isinstance(quantum_instance, Backend): - quantum_instance = QuantumInstance(quantum_instance) - self._quantum_instance = quantum_instance - - @property - def sampler(self) -> BaseSampler | None: - """Get the sampler. - - Returns: - The sampler used to run this algorithm. - """ - return self._sampler - - @sampler.setter - def sampler(self, sampler: BaseSampler) -> None: - """Set the sampler. - - Args: - sampler: The sampler used to run this algorithm. - """ - self._sampler = sampler - - def amplify(self, amplification_problem: AmplificationProblem) -> "GroverResult": - """Run the Grover algorithm. - - Args: - amplification_problem: The amplification problem. - - Returns: - The result as a ``GroverResult``, where e.g. the most likely state can be queried - as ``result.top_measurement``. - - Raises: - ValueError: If a quantum instance or sampler is not set. - AlgorithmError: If a sampler job fails. - TypeError: If ``is_good_state`` is not provided and is required (i.e. when iterations - is ``None`` or a ``list``) - """ - if self._sampler is None and self._quantum_instance is None: - raise ValueError("A quantum instance or sampler must be provided.") - - if self._quantum_instance is not None and self._sampler is not None: - raise ValueError("Only one of quantum_instance or sampler can be passed, not both!") - - if isinstance(self._iterations, list): - max_iterations = len(self._iterations) - max_power = np.inf # no cap on the power - iterator: Iterator[int] = iter(self._iterations) - else: - max_iterations = max(10, 2**amplification_problem.oracle.num_qubits) - max_power = np.ceil( - 2 ** (len(amplification_problem.grover_operator.reflection_qubits) / 2) - ) - iterator = self._iterations - - result = GroverResult() - - iterations = [] - top_measurement = "0" * len(amplification_problem.objective_qubits) - oracle_evaluation = False - all_circuit_results = [] - max_probability = 0 - shots = 0 - - for _ in range(max_iterations): # iterate at most to the max number of iterations - # get next power and check if allowed - power = next(iterator) - - if power > max_power: - break - - iterations.append(power) # store power - - # sample from [0, power) if specified - if self._sample_from_iterations: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - power = algorithm_globals.random.integers(power) - # Run a grover experiment for a given power of the Grover operator. - if self._sampler is not None: - qc = self.construct_circuit(amplification_problem, power, measurement=True) - job = self._sampler.run([qc]) - - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Sampler job failed.") from exc - - num_bits = len(amplification_problem.objective_qubits) - circuit_results: dict[str, Any] | Statevector | np.ndarray = { - np.binary_repr(k, num_bits): v for k, v in results.quasi_dists[0].items() - } - top_measurement, max_probability = max(circuit_results.items(), key=lambda x: x[1]) - - else: # use of else brach instead of elif as this seperates out the deprecated logic - if self._quantum_instance.is_statevector: - qc = self.construct_circuit(amplification_problem, power, measurement=False) - circuit_results = self._quantum_instance.execute(qc).get_statevector() - num_bits = len(amplification_problem.objective_qubits) - - # trace out work qubits - if qc.width() != num_bits: - indices = [ - i - for i in range(qc.num_qubits) - if i not in amplification_problem.objective_qubits - ] - rho = partial_trace(circuit_results, indices) - circuit_results = np.diag(rho.data) - - max_amplitude = max(circuit_results.max(), circuit_results.min(), key=abs) - max_amplitude_idx = np.where(circuit_results == max_amplitude)[0][0] - top_measurement = np.binary_repr(max_amplitude_idx, num_bits) - max_probability = np.abs(max_amplitude) ** 2 - shots = 1 - else: - qc = self.construct_circuit(amplification_problem, power, measurement=True) - circuit_results = self._quantum_instance.execute(qc).get_counts(qc) - top_measurement = max(circuit_results.items(), key=operator.itemgetter(1))[0] - shots = sum(circuit_results.values()) - max_probability = ( - max(circuit_results.items(), key=operator.itemgetter(1))[1] / shots - ) - - all_circuit_results.append(circuit_results) - - if (isinstance(self._iterations_arg, int)) and ( - amplification_problem.is_good_state is None - ): - oracle_evaluation = None # cannot check for good state without is_good_state arg - break - - # is_good_state arg must be provided if iterations arg is not an integer - if ( - self._iterations_arg is None or isinstance(self._iterations_arg, list) - ) and amplification_problem.is_good_state is None: - raise TypeError("An is_good_state function is required with the provided oracle") - - # only check if top measurement is a good state if an is_good_state arg is provided - oracle_evaluation = amplification_problem.is_good_state(top_measurement) - - if oracle_evaluation is True: - break # we found a solution - - result.iterations = iterations - result.top_measurement = top_measurement - result.assignment = amplification_problem.post_processing(top_measurement) - result.oracle_evaluation = oracle_evaluation - result.circuit_results = all_circuit_results - result.max_probability = max_probability - - return result - - @staticmethod - def optimal_num_iterations(num_solutions: int, num_qubits: int) -> int: - """Return the optimal number of iterations, if the number of solutions is known. - - Args: - num_solutions: The number of solutions. - num_qubits: The number of qubits used to encode the states. - - Returns: - The optimal number of iterations for Grover's algorithm to succeed. - """ - amplitude = np.sqrt(num_solutions / 2**num_qubits) - return round(np.arccos(amplitude) / (2 * np.arcsin(amplitude))) - - def construct_circuit( - self, problem: AmplificationProblem, power: int | None = None, measurement: bool = False - ) -> QuantumCircuit: - """Construct the circuit for Grover's algorithm with ``power`` Grover operators. - - Args: - problem: The amplification problem for the algorithm. - power: The number of times the Grover operator is repeated. If None, this argument - is set to the first item in ``iterations``. - measurement: Boolean flag to indicate if measurement should be included in the circuit. - - Returns: - QuantumCircuit: the QuantumCircuit object for the constructed circuit - - Raises: - ValueError: If no power is passed and the iterations are not an integer. - """ - if power is None: - if len(self._iterations) > 1: - raise ValueError("Please pass ``power`` if the iterations are not an integer.") - power = self._iterations[0] - - qc = QuantumCircuit(problem.oracle.num_qubits, name="Grover circuit") - qc.compose(problem.state_preparation, inplace=True) - if power > 0: - qc.compose(problem.grover_operator.power(power), inplace=True) - - if measurement: - measurement_cr = ClassicalRegister(len(problem.objective_qubits)) - qc.add_register(measurement_cr) - qc.measure(problem.objective_qubits, measurement_cr) - - return qc - - -class GroverResult(AmplitudeAmplifierResult): - """Grover Result.""" - - def __init__(self) -> None: - super().__init__() - self._iterations: list[int] | None = None - - @property - def iterations(self) -> list[int]: - """All the powers of the Grover operator that have been tried. - - Returns: - The powers of the Grover operator tested. - """ - return self._iterations - - @iterations.setter - def iterations(self, value: list[int]) -> None: - """Set the powers of the Grover operator that have been tried. - - Args: - value: A new value for the powers. - """ - self._iterations = value diff --git a/qiskit/algorithms/amplitude_estimators/__init__.py b/qiskit/algorithms/amplitude_estimators/__init__.py deleted file mode 100644 index 764f8863857d..000000000000 --- a/qiskit/algorithms/amplitude_estimators/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Amplitude Estimators package.""" - -from .amplitude_estimator import AmplitudeEstimator, AmplitudeEstimatorResult -from .ae import AmplitudeEstimation, AmplitudeEstimationResult -from .fae import FasterAmplitudeEstimation, FasterAmplitudeEstimationResult -from .iae import IterativeAmplitudeEstimation, IterativeAmplitudeEstimationResult -from .mlae import MaximumLikelihoodAmplitudeEstimation, MaximumLikelihoodAmplitudeEstimationResult -from .estimation_problem import EstimationProblem - -__all__ = [ - "AmplitudeEstimator", - "AmplitudeEstimatorResult", - "AmplitudeEstimation", - "AmplitudeEstimationResult", - "FasterAmplitudeEstimation", - "FasterAmplitudeEstimationResult", - "IterativeAmplitudeEstimation", - "IterativeAmplitudeEstimationResult", - "MaximumLikelihoodAmplitudeEstimation", - "MaximumLikelihoodAmplitudeEstimationResult", - "EstimationProblem", -] diff --git a/qiskit/algorithms/amplitude_estimators/ae.py b/qiskit/algorithms/amplitude_estimators/ae.py deleted file mode 100644 index c454cb18793b..000000000000 --- a/qiskit/algorithms/amplitude_estimators/ae.py +++ /dev/null @@ -1,688 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Quantum Phase Estimation-based Amplitude Estimation algorithm.""" - -from __future__ import annotations -from collections import OrderedDict -import warnings -import numpy as np -from scipy.stats import chi2, norm -from scipy.optimize import bisect - -from qiskit import QuantumCircuit, ClassicalRegister -from qiskit.providers import Backend -from qiskit.primitives import BaseSampler -from qiskit.utils import QuantumInstance -from qiskit.utils.deprecation import deprecate_arg, deprecate_func -from .amplitude_estimator import AmplitudeEstimator, AmplitudeEstimatorResult -from .ae_utils import pdf_a, derivative_log_pdf_a, bisect_max -from .estimation_problem import EstimationProblem -from ..exceptions import AlgorithmError - - -class AmplitudeEstimation(AmplitudeEstimator): - r"""The Quantum Phase Estimation-based Amplitude Estimation algorithm. - - This class implements the original Quantum Amplitude Estimation (QAE) algorithm, introduced by - [1]. This canonical version uses quantum phase estimation along with a set of :math:`m` - additional evaluation qubits to find an estimate :math:`\tilde{a}`, that is restricted to the - grid - - .. math:: - - \tilde{a} \in \{\sin^2(\pi y / 2^m) : y = 0, ..., 2^{m-1}\} - - More evaluation qubits produce a finer sampling grid, therefore the accuracy of the algorithm - increases with :math:`m`. - - Using a maximum likelihood post processing, this grid constraint can be circumvented. - This improved estimator is implemented as well, see [2] Appendix A for more detail. - - .. note:: - - This class does not support the :attr:`.EstimationProblem.is_good_state` property, - as for phase estimation-based QAE, the oracle that identifes the good states - must be encoded in the Grover operator. To set custom oracles, the - :attr:`.EstimationProblem.grover_operator` attribute can be set directly. - - References: - [1]: Brassard, G., Hoyer, P., Mosca, M., & Tapp, A. (2000). - Quantum Amplitude Amplification and Estimation. - `arXiv:quant-ph/0005055 `_. - [2]: Grinko, D., Gacon, J., Zoufal, C., & Woerner, S. (2019). - Iterative Quantum Amplitude Estimation. - `arXiv:1912.05559 `_. - """ - - @deprecate_arg( - "quantum_instance", - additional_msg=( - "Instead, use the ``sampler`` argument. See https://qisk.it/algo_migration for a " - "migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - num_eval_qubits: int, - phase_estimation_circuit: QuantumCircuit | None = None, - iqft: QuantumCircuit | None = None, - quantum_instance: QuantumInstance | Backend | None = None, - sampler: BaseSampler | None = None, - ) -> None: - r""" - Args: - num_eval_qubits: The number of evaluation qubits. - phase_estimation_circuit: The phase estimation circuit used to run the algorithm. - Defaults to the standard phase estimation circuit from the circuit library, - `qiskit.circuit.library.PhaseEstimation` when None. - iqft: The inverse quantum Fourier transform component, defaults to using a standard - implementation from `qiskit.circuit.library.QFT` when None. - quantum_instance: Deprecated: The backend (or `QuantumInstance`) to execute - the circuits on. - sampler: A sampler primitive to evaluate the circuits. - - Raises: - ValueError: If the number of evaluation qubits is smaller than 1. - """ - if num_eval_qubits < 1: - raise ValueError("The number of evaluation qubits must at least be 1.") - - super().__init__() - - # set quantum instance - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - self.quantum_instance = quantum_instance - - # get parameters - self._m = num_eval_qubits - self._M = 2**num_eval_qubits # pylint: disable=invalid-name - - self._iqft = iqft - self._pec = phase_estimation_circuit - self._sampler = sampler - - @property - def sampler(self) -> BaseSampler | None: - """Get the sampler primitive. - - Returns: - The sampler primitive to evaluate the circuits. - """ - return self._sampler - - @sampler.setter - def sampler(self, sampler: BaseSampler) -> None: - """Set sampler primitive. - - Args: - sampler: A sampler primitive to evaluate the circuits. - """ - self._sampler = sampler - - @property - @deprecate_func( - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - since="0.24.0", - is_property=True, - ) - def quantum_instance(self) -> QuantumInstance | None: - """Deprecated: Get the quantum instance. - - Returns: - The quantum instance used to run this algorithm. - """ - return self._quantum_instance - - @quantum_instance.setter - @deprecate_func( - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - since="0.24.0", - is_property=True, - ) - def quantum_instance(self, quantum_instance: QuantumInstance | Backend) -> None: - """Deprecated: Set quantum instance. - - Args: - quantum_instance: The quantum instance used to run this algorithm. - """ - if isinstance(quantum_instance, Backend): - quantum_instance = QuantumInstance(quantum_instance) - self._quantum_instance = quantum_instance - - def construct_circuit( - self, estimation_problem: EstimationProblem, measurement: bool = False - ) -> QuantumCircuit: - """Construct the Amplitude Estimation quantum circuit. - - Args: - estimation_problem: The estimation problem for which to construct the QAE circuit. - measurement: Boolean flag to indicate if measurements should be included in the circuit. - - Returns: - The QuantumCircuit object for the constructed circuit. - """ - # use custom Phase Estimation circuit if provided - if self._pec is not None: - pec = self._pec - - # otherwise use the circuit library -- note that this does not include the A operator - else: - from qiskit.circuit.library import PhaseEstimation - - pec = PhaseEstimation(self._m, estimation_problem.grover_operator, iqft=self._iqft) - - # combine the Phase Estimation circuit with the A operator - circuit = QuantumCircuit(*pec.qregs) - circuit.compose( - estimation_problem.state_preparation, - list(range(self._m, circuit.num_qubits)), - inplace=True, - ) - circuit.compose(pec, inplace=True) - - # add measurements if necessary - if measurement: - cr = ClassicalRegister(self._m) - circuit.add_register(cr) - circuit.measure(list(range(self._m)), list(range(self._m))) - - return circuit - - def evaluate_measurements( - self, - circuit_results: dict[str, int] | np.ndarray, - threshold: float = 1e-6, - ) -> tuple[dict[float, float], dict[int, float]]: - """Evaluate the results from the circuit simulation. - - Given the probabilities from statevector simulation of the QAE circuit, compute the - probabilities that the measurements y/gridpoints a are the best estimate. - - Args: - circuit_results: The circuit result from the QAE circuit. Can be either a counts dict - or a statevector or a quasi-probabilities dict. - threshold: Measurements with probabilities below the threshold are discarded. - - Returns: - Dictionaries containing the a gridpoints with respective probabilities and - y measurements with respective probabilities, in this order. - """ - # compute grid sample and measurement dicts - if isinstance(circuit_results, dict): - if set(map(type, circuit_results.values())) == {int}: - samples, measurements = self._evaluate_count_results(circuit_results) - else: - samples, measurements = self._evaluate_quasi_probabilities_results(circuit_results) - else: - samples, measurements = self._evaluate_statevector_results(circuit_results) - - # cutoff probabilities below the threshold - samples = {a: p for a, p in samples.items() if p > threshold} - measurements = {y: p for y, p in measurements.items() if p > threshold} - - return samples, measurements - - def _evaluate_statevector_results(self, statevector): - # map measured results to estimates - measurements = OrderedDict() # type: OrderedDict - num_qubits = int(np.log2(len(statevector))) - for i, amplitude in enumerate(statevector): - b = bin(i)[2:].zfill(num_qubits)[::-1] - y = int(b[: self._m], 2) # chop off all except the evaluation qubits - measurements[y] = measurements.get(y, 0) + np.abs(amplitude) ** 2 - - samples = OrderedDict() # type: OrderedDict - for y, probability in measurements.items(): - if y >= int(self._M / 2): - y = self._M - y - # due to the finite accuracy of the sine, we round the result to 7 decimals - a = np.round(np.power(np.sin(y * np.pi / 2**self._m), 2), decimals=7) - samples[a] = samples.get(a, 0) + probability - - return samples, measurements - - def _evaluate_quasi_probabilities_results(self, circuit_results): - # construct probabilities - measurements = OrderedDict() - samples = OrderedDict() - for state, probability in circuit_results.items(): - # reverts the last _m items - y = int(state[: -self._m - 1 : -1], 2) - measurements[y] = probability - a = np.round(np.power(np.sin(y * np.pi / 2**self._m), 2), decimals=7) - samples[a] = samples.get(a, 0.0) + probability - - return samples, measurements - - def _evaluate_count_results(self, counts) -> tuple[dict[float, float], dict[int, float]]: - # construct probabilities - measurements: dict[int, float] = OrderedDict() - samples: dict[float, float] = OrderedDict() - shots = sum(counts.values()) - for state, count in counts.items(): - y = int(state.replace(" ", "")[: self._m][::-1], 2) - probability = count / shots - measurements[y] = probability - a = np.round(np.power(np.sin(y * np.pi / 2**self._m), 2), decimals=7) - samples[a] = samples.get(a, 0.0) + probability - - return samples, measurements - - @staticmethod - def compute_mle( - result: "AmplitudeEstimationResult", apply_post_processing: bool = False - ) -> float: - """Compute the Maximum Likelihood Estimator (MLE). - - Args: - result: An amplitude estimation result object. - apply_post_processing: If True, apply the post processing to the MLE before returning - it. - - Returns: - The MLE for the provided result object. - """ - m = result.num_evaluation_qubits - M = 2**m # pylint: disable=invalid-name - qae = result.estimation - - # likelihood function - a_i = np.asarray(list(result.samples.keys())) - p_i = np.asarray(list(result.samples.values())) - - def loglikelihood(a): - return np.sum(result.shots * p_i * np.log(pdf_a(a_i, a, m))) - - # y is pretty much an integer, but to map 1.9999 to 2 we must first - # use round and then int conversion - y = int(np.round(M * np.arcsin(np.sqrt(qae)) / np.pi)) - - # Compute the two intervals in which are candidates for containing - # the maximum of the log-likelihood function: the two bubbles next to - # the QAE estimate - if y == 0: - right_of_qae = np.sin(np.pi * (y + 1) / M) ** 2 - bubbles = [qae, right_of_qae] - - elif y == int(M / 2): # remember, M = 2^m is a power of 2 - left_of_qae = np.sin(np.pi * (y - 1) / M) ** 2 - bubbles = [left_of_qae, qae] - - else: - left_of_qae = np.sin(np.pi * (y - 1) / M) ** 2 - right_of_qae = np.sin(np.pi * (y + 1) / M) ** 2 - bubbles = [left_of_qae, qae, right_of_qae] - - # Find global maximum amongst the two local maxima - a_opt = qae - loglik_opt = loglikelihood(a_opt) - for a, b in zip(bubbles[:-1], bubbles[1:]): - locmax, val = bisect_max(loglikelihood, a, b, retval=True) - if val > loglik_opt: - a_opt = locmax - loglik_opt = val - - if apply_post_processing: - return result.post_processing(a_opt) - - return a_opt - - def estimate(self, estimation_problem: EstimationProblem) -> "AmplitudeEstimationResult": - """Run the amplitude estimation algorithm on provided estimation problem. - - Args: - estimation_problem: The estimation problem. - - Returns: - An amplitude estimation results object. - - Raises: - ValueError: If `state_preparation` or `objective_qubits` are not set in the - `estimation_problem`. - ValueError: A quantum instance or sampler must be provided. - AlgorithmError: Sampler job run error. - """ - # check if A factory or state_preparation has been set - if estimation_problem.state_preparation is None: - raise ValueError( - "The state_preparation property of the estimation problem must be set." - ) - if self._quantum_instance is None and self._sampler is None: - raise ValueError("A quantum instance or sampler must be provided.") - - if estimation_problem.objective_qubits is None: - raise ValueError("The objective_qubits property of the estimation problem must be set.") - - if estimation_problem.has_good_state: - warnings.warn( - "The AmplitudeEstimation class does not support an is_good_state function to " - "identify good states. For this algorithm, a custom oracle has to be encoded directly " - "in the grover_operator. If no custom oracle is set, this algorithm identifies good " - "states as those, where all objective qubits are in state 1." - ) - - result = AmplitudeEstimationResult() - result.num_evaluation_qubits = self._m - result.post_processing = estimation_problem.post_processing - - shots = 0 - if self._quantum_instance is not None and self._quantum_instance.is_statevector: - circuit = self.construct_circuit(estimation_problem, measurement=False) - # run circuit on statevector simulator - statevector = self._quantum_instance.execute(circuit).get_statevector() - result.circuit_results = statevector - # store number of shots: convention is 1 shot for statevector, - # needed so that MLE works! - shots = 1 - else: - circuit = self.construct_circuit(estimation_problem, measurement=True) - if self._quantum_instance is not None: - # run circuit on QASM simulator - result.circuit_results = self._quantum_instance.execute(circuit).get_counts() - shots = sum(result.circuit_results.values()) - else: - try: - job = self._sampler.run([circuit]) - ret = job.result() - except Exception as exc: - raise AlgorithmError("The job was not completed successfully. ") from exc - - shots = ret.metadata[0].get("shots") - if shots is None: - result.circuit_results = ret.quasi_dists[0].binary_probabilities() - shots = 1 - else: - result.circuit_results = { - k: round(v * shots) - for k, v in ret.quasi_dists[0].binary_probabilities().items() - } - - # store shots - result.shots = shots - samples, measurements = self.evaluate_measurements(result.circuit_results) - - result.samples = samples - result.samples_processed = { - estimation_problem.post_processing(a): p for a, p in samples.items() - } - result.measurements = measurements - - # determine the most likely estimate - result.max_probability = 0 - for amplitude, (mapped, prob) in zip(samples.keys(), result.samples_processed.items()): - if prob > result.max_probability: - result.max_probability = prob - result.estimation = amplitude - result.estimation_processed = mapped - - # store the number of oracle queries - result.num_oracle_queries = result.shots * (self._M - 1) - - # run the MLE post-processing - mle = self.compute_mle(result) - result.mle = mle - result.mle_processed = estimation_problem.post_processing(mle) - - result.confidence_interval = self.compute_confidence_interval(result) - result.confidence_interval_processed = tuple( - estimation_problem.post_processing(value) for value in result.confidence_interval - ) - - return result - - @staticmethod - def compute_confidence_interval( - result: "AmplitudeEstimationResult", alpha: float = 0.05, kind: str = "likelihood_ratio" - ) -> tuple[float, float]: - """Compute the (1 - alpha) confidence interval. - - Args: - result: An amplitude estimation result for which to compute the confidence interval. - alpha: Confidence level: compute the (1 - alpha) confidence interval. - kind: The method to compute the confidence interval, can be 'fisher', 'observed_fisher' - or 'likelihood_ratio' (default) - - Returns: - The (1 - alpha) confidence interval of the specified kind. - - Raises: - NotImplementedError: If the confidence interval method `kind` is not implemented. - """ - # if statevector simulator the estimate is exact - if isinstance(result.circuit_results, (list, np.ndarray)): - return (result.mle, result.mle) - - if kind in ["likelihood_ratio", "lr"]: - return _likelihood_ratio_confint(result, alpha) - - if kind in ["fisher", "fi"]: - return _fisher_confint(result, alpha, observed=False) - - if kind in ["observed_fisher", "observed_information", "oi"]: - return _fisher_confint(result, alpha, observed=True) - - raise NotImplementedError(f"CI `{kind}` is not implemented.") - - -class AmplitudeEstimationResult(AmplitudeEstimatorResult): - """The ``AmplitudeEstimation`` result object.""" - - def __init__(self) -> None: - super().__init__() - self._num_evaluation_qubits: int | None = None - self._mle: float | None = None - self._mle_processed: float | None = None - self._samples: dict[float, float] | None = None - self._samples_processed: dict[float, float] | None = None - self._y_measurements: dict[int, float] | None = None - self._max_probability: float | None = None - - @property - def num_evaluation_qubits(self) -> int: - """Returns the number of evaluation qubits.""" - return self._num_evaluation_qubits - - @num_evaluation_qubits.setter - def num_evaluation_qubits(self, num_evaluation_qubits: int) -> None: - """Set the number of evaluation qubits.""" - self._num_evaluation_qubits = num_evaluation_qubits - - @property - def mle_processed(self) -> float: - """Return the post-processed MLE for the amplitude.""" - return self._mle_processed - - @mle_processed.setter - def mle_processed(self, value: float) -> None: - """Set the post-processed MLE for the amplitude.""" - self._mle_processed = value - - @property - def samples_processed(self) -> dict[float, float]: - """Return the post-processed measurement samples with their measurement probability.""" - return self._samples_processed - - @samples_processed.setter - def samples_processed(self, value: dict[float, float]) -> None: - """Set the post-processed measurement samples.""" - self._samples_processed = value - - @property - def mle(self) -> float: - r"""Return the MLE for the amplitude, in $[0, 1]$.""" - return self._mle - - @mle.setter - def mle(self, value: float) -> None: - r"""Set the MLE for the amplitude, in $[0, 1]$.""" - self._mle = value - - @property - def samples(self) -> dict[float, float]: - """Return the measurement samples with their measurement probability.""" - return self._samples - - @samples.setter - def samples(self, value: dict[float, float]) -> None: - """Set the measurement samples with their measurement probability.""" - self._samples = value - - @property - def measurements(self) -> dict[int, float]: - """Return the measurements as integers with their measurement probability.""" - return self._y_measurements - - @measurements.setter - def measurements(self, value: dict[int, float]) -> None: - """Set the measurements as integers with their measurement probability.""" - self._y_measurements = value - - @property - def max_probability(self) -> float: - """Return the maximum sampling probability.""" - return self._max_probability - - @max_probability.setter - def max_probability(self, value: float) -> None: - """Set the maximum sampling probability.""" - self._max_probability = value - - -def _compute_fisher_information(result: AmplitudeEstimationResult, observed: bool = False) -> float: - """Computes the Fisher information for the output of the previous run. - - Args: - result: An amplitude estimation result for which to compute the confidence interval. - observed: If True, the observed Fisher information is returned, otherwise - the expected Fisher information. - - Returns: - The Fisher information. - """ - fisher_information = None - mlv = result.mle # MLE in [0,1] - m = result.num_evaluation_qubits - M = 2**m # pylint: disable=invalid-name - - if observed: - a_i = np.asarray(list(result.samples.keys())) - p_i = np.asarray(list(result.samples.values())) - - # Calculate the observed Fisher information - fisher_information = sum(p * derivative_log_pdf_a(a, mlv, m) ** 2 for p, a in zip(p_i, a_i)) - else: - - def integrand(x): - return (derivative_log_pdf_a(x, mlv, m)) ** 2 * pdf_a(x, mlv, m) - - grid = np.sin(np.pi * np.arange(M / 2 + 1) / M) ** 2 - fisher_information = sum(integrand(x) for x in grid) - - return fisher_information - - -def _fisher_confint( - result: AmplitudeEstimationResult, alpha: float, observed: bool = False -) -> tuple[float, float]: - """Compute the Fisher information confidence interval for the MLE of the previous run. - - Args: - result: An amplitude estimation result for which to compute the confidence interval. - alpha: Specifies the (1 - alpha) confidence level (0 < alpha < 1). - observed: If True, the observed Fisher information is used to construct the - confidence interval, otherwise the expected Fisher information. - - Returns: - The Fisher information confidence interval. - """ - # approximate the standard deviation of the MLE and construct the confidence interval - std = np.sqrt(result.shots * _compute_fisher_information(result, observed)) - confint = result.mle + norm.ppf(1 - alpha / 2) / std * np.array([-1, 1]) - - # transform the confidence interval from [0, 1] to the target interval - return result.post_processing(confint[0]), result.post_processing(confint[1]) - - -def _likelihood_ratio_confint( - result: AmplitudeEstimationResult, alpha: float -) -> tuple[float, float]: - """Compute the likelihood ratio confidence interval for the MLE of the previous run. - - Args: - result: An amplitude estimation result for which to compute the confidence interval. - alpha: Specifies the (1 - alpha) confidence level (0 < alpha < 1). - - Returns: - The likelihood ratio confidence interval. - """ - # Compute the two intervals in which we the look for values above - # the likelihood ratio: the two bubbles next to the QAE estimate - m = result.num_evaluation_qubits - M = 2**m # pylint: disable=invalid-name - qae = result.estimation - - y = int(np.round(M * np.arcsin(np.sqrt(qae)) / np.pi)) - if y == 0: - right_of_qae = np.sin(np.pi * (y + 1) / M) ** 2 - bubbles = [qae, right_of_qae] - - elif y == int(M / 2): # remember, M = 2^m is a power of 2 - left_of_qae = np.sin(np.pi * (y - 1) / M) ** 2 - bubbles = [left_of_qae, qae] - - else: - left_of_qae = np.sin(np.pi * (y - 1) / M) ** 2 - right_of_qae = np.sin(np.pi * (y + 1) / M) ** 2 - bubbles = [left_of_qae, qae, right_of_qae] - - # likelihood function - a_i = np.asarray(list(result.samples.keys())) - p_i = np.asarray(list(result.samples.values())) - - def loglikelihood(a): - return np.sum(result.shots * p_i * np.log(pdf_a(a_i, a, m))) - - # The threshold above which the likelihoods are in the - # confidence interval - loglik_mle = loglikelihood(result.mle) - thres = loglik_mle - chi2.ppf(1 - alpha, df=1) / 2 - - def cut(x): - return loglikelihood(x) - thres - - # Store the boundaries of the confidence interval - # It's valid to start off with the zero-width confidence interval, since the maximum - # of the likelihood function is guaranteed to be over the threshold, and if alpha = 0 - # that's the valid interval - lower = upper = result.mle - - # Check the two intervals/bubbles: check if they surpass the - # threshold and if yes add the part that does to the CI - for a, b in zip(bubbles[:-1], bubbles[1:]): - # Compute local maximum and perform a bisect search between - # the local maximum and the bubble boundaries - locmax, val = bisect_max(loglikelihood, a, b, retval=True) - if val >= thres: - # Bisect pre-condition is that the function has different - # signs at the boundaries of the interval we search in - if cut(a) * cut(locmax) < 0: - left = bisect(cut, a, locmax) - lower = np.minimum(lower, left) - if cut(locmax) * cut(b) < 0: - right = bisect(cut, locmax, b) - upper = np.maximum(upper, right) - - # Put together CI - return result.post_processing(lower), result.post_processing(upper) diff --git a/qiskit/algorithms/amplitude_estimators/ae_utils.py b/qiskit/algorithms/amplitude_estimators/ae_utils.py deleted file mode 100644 index bd695be45a63..000000000000 --- a/qiskit/algorithms/amplitude_estimators/ae_utils.py +++ /dev/null @@ -1,258 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Utils for the Maximum-Likelihood estimation used in ``AmplitudeEstimation``.""" - -import logging -import numpy as np - -logger = logging.getLogger(__name__) - -# pylint: disable=invalid-name - - -def bisect_max(f, a, b, steps=50, minwidth=1e-12, retval=False): - """Find the maximum of the real-valued function f in the interval [a, b] using bisection. - - Args: - f (callable): the function to find the maximum of - a (float): the lower limit of the interval - b (float): the upper limit of the interval - steps (int): the maximum number of steps in the bisection - minwidth (float): if the current interval is smaller than minwidth stop - the search - retval (bool): return value - - Returns: - float: The maximum of f in [a,b] according to this algorithm. - """ - it = 0 - m = (a + b) / 2 - fm = 0 - while it < steps and b - a > minwidth: - l, r = (a + m) / 2, (m + b) / 2 - fl, fm, fr = f(l), f(m), f(r) - - # fl is the maximum - if fl > fm and fl > fr: - b = m - m = l - # fr is the maximum - elif fr > fm and fr > fl: - a = m - m = r - # fm is the maximum - else: - a = l - b = r - - it += 1 - - if it == steps: - logger.warning("-- Warning, bisect_max didn't converge after %s steps", steps) - - if retval: - return m, fm - - return m - - -def _circ_dist(x, p): - r"""Circumferential distance function. - - For two angles :math:`x` and :math:`p` on the unit circuit this function is defined as - - .. math:: - - d(x, p) = \min_{z \in [-1, 0, 1]} |z + p - x| - - Args: - x (float): first angle - p (float): second angle - - Returns: - float: d(x, p) - """ - t = p - x - # Since x and p \in [0,1] it suffices to check not all integers - # but only -1, 0 and 1 - z = np.array([-1, 0, 1]) - - if hasattr(t, "__len__"): - d = np.empty_like(t) - for idx, ti in enumerate(t): - d[idx] = np.min(np.abs(z + ti)) - return d - - return np.min(np.abs(z + t)) - - -def _derivative_circ_dist(x, p): - """Derivative of circumferential distance function. - - Args: - x (float): first angle - p (float): second angle - - Returns: - float: The derivative. - """ - # pylint: disable=chained-comparison,misplaced-comparison-constant - t = p - x - if t < -0.5 or (0 < t and t < 0.5): - return -1 - if t > 0.5 or (-0.5 < t and t < 0): - return 1 - return 0 - - -def _amplitude_to_angle(a): - r"""Transform from the amplitude :math:`a \in [0, 1]` to the generating angle. - - In QAE, the amplitude can be written from a generating angle :math:`\omega` as - - .. math: - - a = \sin^2(\pi \omega) - - This returns the :math:`\omega` for a given :math:`a`. - - Args: - a (float): A value in :math:`[0,1]`. - - Returns: - float: :math:`\sin^{-1}(\sqrt{a}) / \pi` - """ - return np.arcsin(np.sqrt(a)) / np.pi - - -def _derivative_amplitude_to_angle(a): - """Compute the derivative of ``amplitude_to_angle``.""" - return 1 / (2 * np.pi * np.sqrt((1 - a) * a)) - - -def _alpha(x, p): - """Helper function for `pdf_a`, alpha = pi * d(omega(x), omega(p)). - - Here, omega(x) is `_amplitude_to_angle(x)`. - """ - omega = _amplitude_to_angle - return np.pi * _circ_dist(omega(x), omega(p)) - - -def _derivative_alpha(x, p): - """Compute the derivative of alpha.""" - omega = _amplitude_to_angle - d_omega = _derivative_amplitude_to_angle - return np.pi * _derivative_circ_dist(omega(x), omega(p)) * d_omega(p) - - -def _beta(x, p): - """Helper function for `pdf_a`, beta = pi * d(1 - omega(x), omega(p)).""" - omega = _amplitude_to_angle - return np.pi * _circ_dist(1 - omega(x), omega(p)) - - -def _derivative_beta(x, p): - """Compute the derivative of beta.""" - omega = _amplitude_to_angle - d_omega = _derivative_amplitude_to_angle - return np.pi * _derivative_circ_dist(1 - omega(x), omega(p)) * d_omega(p) - - -def _pdf_a_single_angle(x, p, m, pi_delta): - """Helper function for `pdf_a`.""" - M = 2**m - - d = pi_delta(x, p) - res = np.sin(M * d) ** 2 / (M * np.sin(d)) ** 2 if d != 0 else 1 - - return res - - -def pdf_a(x, p, m): - """ - Return the PDF of a, i.e. the probability of getting the estimate x - (in [0, 1]) if p (in [0, 1]) is the true value, given that we use m qubits. - - Args: - x (float): the grid point - p (float): the true value - m (float): the number of evaluation qubits - - Returns: - float: PDF(x|p) - """ - # We'll use list comprehension, so the input should be a list - scalar = False - if not hasattr(x, "__len__"): - scalar = True - x = np.asarray([x]) - - # Compute the probabilities: Add up both angles that produce the given - # value, except for the angles 0 and 0.5, which map to the unique a-values, - # 0 and 1, respectively - pr = np.array( - [ - _pdf_a_single_angle(xi, p, m, _alpha) + _pdf_a_single_angle(xi, p, m, _beta) - if (xi not in [0, 1]) - else _pdf_a_single_angle(xi, p, m, _alpha) - for xi in x - ] - ).flatten() - - # If is was a scalar return scalar otherwise the array - return pr[0] if scalar else pr - - -def derivative_log_pdf_a(x, p, m): - """ - Return the derivative of the logarithm of the PDF of a. - - Args: - x (float): the grid point - p (float): the true value - m (float): the number of evaluation qubits - - Returns: - float: d/dp log(PDF(x|p)) - """ - M = 2**m - - if x not in [0, 1]: - num_p1 = 0 - for A, dA, B, dB in zip( - [_alpha, _beta], - [_derivative_alpha, _derivative_beta], - [_beta, _alpha], - [_derivative_beta, _derivative_alpha], - ): - num_p1 += 2 * M * np.sin(M * A(x, p)) * np.cos(M * A(x, p)) * dA(x, p) * np.sin( - B(x, p) - ) ** 2 + 2 * np.sin(M * A(x, p)) ** 2 * np.sin(B(x, p)) * np.cos(B(x, p)) * dB(x, p) - - den_p1 = ( - np.sin(M * _alpha(x, p)) ** 2 * np.sin(_beta(x, p)) ** 2 - + np.sin(M * _beta(x, p)) ** 2 * np.sin(_alpha(x, p)) ** 2 - ) - - num_p2 = 0 - for A, dA, B in zip( - [_alpha, _beta], [_derivative_alpha, _derivative_beta], [_beta, _alpha] - ): - num_p2 += 2 * np.cos(A(x, p)) * dA(x, p) * np.sin(B(x, p)) - - den_p2 = np.sin(_alpha(x, p)) * np.sin(_beta(x, p)) - - return num_p1 / den_p1 - num_p2 / den_p2 - - return 2 * _derivative_alpha(x, p) * (M / np.tan(M * _alpha(x, p)) - 1 / np.tan(_alpha(x, p))) diff --git a/qiskit/algorithms/amplitude_estimators/amplitude_estimator.py b/qiskit/algorithms/amplitude_estimators/amplitude_estimator.py deleted file mode 100644 index 613827c6ccd6..000000000000 --- a/qiskit/algorithms/amplitude_estimators/amplitude_estimator.py +++ /dev/null @@ -1,131 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Amplitude Estimation interface.""" - -from __future__ import annotations -from abc import abstractmethod, ABC -from collections.abc import Callable - -import numpy as np - -from .estimation_problem import EstimationProblem -from ..algorithm_result import AlgorithmResult - - -class AmplitudeEstimator(ABC): - """The Amplitude Estimation interface.""" - - @abstractmethod - def estimate(self, estimation_problem: EstimationProblem) -> "AmplitudeEstimatorResult": - """Run the amplitude estimation algorithm. - - Args: - estimation_problem: An ``EstimationProblem`` containing all problem-relevant information - such as the state preparation and the objective qubits. - """ - raise NotImplementedError - - -class AmplitudeEstimatorResult(AlgorithmResult): - """The results object for amplitude estimation algorithms.""" - - def __init__(self) -> None: - super().__init__() - self._circuit_results: np.ndarray | dict[str, int] | None = None - self._shots: int | None = None - self._estimation: float | None = None - self._estimation_processed: float | None = None - self._num_oracle_queries: int | None = None - self._post_processing: Callable[[float], float] | None = None - self._confidence_interval: tuple[float, float] | None = None - self._confidence_interval_processed: tuple[float, float] | None = None - - @property - def circuit_results(self) -> np.ndarray | dict[str, int] | None: - """Return the circuit results. Can be a statevector or counts dictionary.""" - return self._circuit_results - - @circuit_results.setter - def circuit_results(self, value: np.ndarray | dict[str, int]) -> None: - """Set the circuit results.""" - self._circuit_results = value - - @property - def shots(self) -> int: - """Return the number of shots used. Is 1 for statevector-based simulations.""" - return self._shots - - @shots.setter - def shots(self, value: int) -> None: - """Set the number of shots used.""" - self._shots = value - - @property - def estimation(self) -> float: - r"""Return the estimation for the amplitude in :math:`[0, 1]`.""" - return self._estimation - - @estimation.setter - def estimation(self, value: float) -> None: - r"""Set the estimation for the amplitude in :math:`[0, 1]`.""" - self._estimation = value - - @property - def estimation_processed(self) -> float: - """Return the estimation for the amplitude after the post-processing has been applied.""" - return self._estimation_processed - - @estimation_processed.setter - def estimation_processed(self, value: float) -> None: - """Set the estimation for the amplitude after the post-processing has been applied.""" - self._estimation_processed = value - - @property - def num_oracle_queries(self) -> int: - """Return the number of Grover oracle queries.""" - return self._num_oracle_queries - - @num_oracle_queries.setter - def num_oracle_queries(self, value: int) -> None: - """Set the number of Grover oracle queries.""" - self._num_oracle_queries = value - - @property - def post_processing(self) -> Callable[[float], float]: - """Return a handle to the post processing function.""" - return self._post_processing - - @post_processing.setter - def post_processing(self, post_processing: Callable[[float], float]) -> None: - """Set a handle to the post processing function.""" - self._post_processing = post_processing - - @property - def confidence_interval(self) -> tuple[float, float]: - """Return the confidence interval for the amplitude (95% interval by default).""" - return self._confidence_interval - - @confidence_interval.setter - def confidence_interval(self, confidence_interval: tuple[float, float]) -> None: - """Set the confidence interval for the amplitude (95% interval by default).""" - self._confidence_interval = confidence_interval - - @property - def confidence_interval_processed(self) -> tuple[float, float]: - """Return the post-processed confidence interval (95% interval by default).""" - return self._confidence_interval_processed - - @confidence_interval_processed.setter - def confidence_interval_processed(self, confidence_interval: tuple[float, float]) -> None: - """Set the post-processed confidence interval (95% interval by default).""" - self._confidence_interval_processed = confidence_interval diff --git a/qiskit/algorithms/amplitude_estimators/estimation_problem.py b/qiskit/algorithms/amplitude_estimators/estimation_problem.py deleted file mode 100644 index 72e9418f72d6..000000000000 --- a/qiskit/algorithms/amplitude_estimators/estimation_problem.py +++ /dev/null @@ -1,274 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Estimation problem class.""" - -from __future__ import annotations -import warnings -from collections.abc import Callable - -import numpy - -from qiskit.circuit import QuantumCircuit, QuantumRegister -from qiskit.circuit.library import GroverOperator - - -class EstimationProblem: - """The estimation problem is the input to amplitude estimation algorithm. - - This class contains all problem-specific information required to run an amplitude estimation - algorithm. That means, it minimally contains the state preparation and the specification - of the good state. It can further hold some post processing on the estimation of the amplitude - or a custom Grover operator. - """ - - def __init__( - self, - state_preparation: QuantumCircuit, - objective_qubits: int | list[int], - grover_operator: QuantumCircuit | None = None, - post_processing: Callable[[float], float] | None = None, - is_good_state: Callable[[str], bool] | None = None, - ) -> None: - r""" - Args: - state_preparation: A circuit preparing the input state, referred to as - :math:`\mathcal{A}`. - objective_qubits: A single qubit index or a list of qubit indices to specify which - qubits to measure. The ``is_good_state`` function is applied on the bitstring of - these objective qubits. - grover_operator: The Grover operator :math:`\mathcal{Q}` used as unitary in the - phase estimation circuit. - post_processing: A mapping applied to the result of the algorithm - :math:`0 \leq a \leq 1`, usually used to map the estimate to a target interval. - Defaults to the identity. - is_good_state: A function to check whether a string represents a good state. Defaults - to all objective qubits being in state :math:`|1\rangle`. - """ - self._state_preparation = state_preparation - self._objective_qubits = objective_qubits - self._grover_operator = grover_operator - self._post_processing = post_processing - self._is_good_state = is_good_state - - @property - def state_preparation(self) -> QuantumCircuit | None: - r"""Get the :math:`\mathcal{A}` operator encoding the amplitude :math:`a`. - - Returns: - The :math:`\mathcal{A}` operator as `QuantumCircuit`. - """ - return self._state_preparation - - @state_preparation.setter - def state_preparation(self, state_preparation: QuantumCircuit) -> None: - r"""Set the :math:`\mathcal{A}` operator, that encodes the amplitude to be estimated. - - Args: - state_preparation: The new :math:`\mathcal{A}` operator. - """ - self._state_preparation = state_preparation - - @property - def objective_qubits(self) -> list[int]: - """Get the criterion for a measurement outcome to be in a 'good' state. - - Returns: - The criterion as list of qubit indices. - """ - if isinstance(self._objective_qubits, int): - return [self._objective_qubits] - - return self._objective_qubits - - @objective_qubits.setter - def objective_qubits(self, objective_qubits: int | list[int]) -> None: - """Set the criterion for a measurement outcome to be in a 'good' state. - - Args: - objective_qubits: The criterion as callable of list of qubit indices. - """ - self._objective_qubits = objective_qubits - - @property - def post_processing(self) -> Callable[[float], float]: - """Apply post processing to the input value. - - Returns: - A handle to the post processing function. Acts as identity by default. - """ - if self._post_processing is None: - return lambda x: x - - return self._post_processing - - @post_processing.setter - def post_processing(self, post_processing: Callable[[float], float] | None) -> None: - """Set the post processing function. - - Args: - post_processing: A handle to the post processing function. If set to ``None``, the - identity will be used as post processing. - """ - self._post_processing = post_processing - - @property - def has_good_state(self) -> bool: - """Check whether an :attr:`is_good_state` function is set. - - Some amplitude estimators, such as :class:`.AmplitudeEstimation` do not support - a custom implementation of the :attr:`is_good_state` function, and can only handle - the default. - - Returns: - ``True``, if a custom :attr:`is_good_state` is set, otherwise returns ``False``. - """ - return self._is_good_state is not None - - @property - def is_good_state(self) -> Callable[[str], bool]: - """Checks whether a bitstring represents a good state. - - Returns: - Handle to the ``is_good_state`` callable. - """ - if self._is_good_state is None: - return lambda x: all(bit == "1" for bit in x) - - return self._is_good_state - - @is_good_state.setter - def is_good_state(self, is_good_state: Callable[[str], bool] | None) -> None: - """Set the ``is_good_state`` function. - - Args: - is_good_state: A function to determine whether a bitstring represents a good state. - If set to ``None``, the good state will be defined as all bits being one. - """ - self._is_good_state = is_good_state - - @property - def grover_operator(self) -> QuantumCircuit | None: - r"""Get the :math:`\mathcal{Q}` operator, or Grover operator. - - If the Grover operator is not set, we try to build it from the :math:`\mathcal{A}` operator - and `objective_qubits`. This only works if `objective_qubits` is a list of integers. - - Returns: - The Grover operator, or None if neither the Grover operator nor the - :math:`\mathcal{A}` operator is set. - """ - if self._grover_operator is not None: - return self._grover_operator - - # build the reflection about the bad state: a MCZ with open controls (thus X gates - # around the controls) and X gates around the target to change from a phaseflip on - # |1> to a phaseflip on |0> - num_state_qubits = self.state_preparation.num_qubits - self.state_preparation.num_ancillas - - oracle = QuantumCircuit(num_state_qubits) - oracle.h(self.objective_qubits[-1]) - if len(self.objective_qubits) == 1: - oracle.x(self.objective_qubits[0]) - else: - oracle.mcx(self.objective_qubits[:-1], self.objective_qubits[-1]) - oracle.h(self.objective_qubits[-1]) - - # construct the grover operator - return GroverOperator(oracle, self.state_preparation) - - @grover_operator.setter - def grover_operator(self, grover_operator: QuantumCircuit | None) -> None: - r"""Set the :math:`\mathcal{Q}` operator. - - Args: - grover_operator: The new :math:`\mathcal{Q}` operator. If set to ``None``, - the default construction via ``qiskit.circuit.library.GroverOperator`` is used. - """ - self._grover_operator = grover_operator - - def rescale(self, scaling_factor: float) -> "EstimationProblem": - """Rescale the good state amplitude in the estimation problem. - - Args: - scaling_factor: The scaling factor in [0, 1]. - - Returns: - A rescaled estimation problem. - """ - if self._grover_operator is not None: - warnings.warn("Rescaling discards the Grover operator.") - - # rescale the amplitude by a factor of 1/4 by adding an auxiliary qubit - rescaled_stateprep = _rescale_amplitudes(self.state_preparation, scaling_factor) - num_qubits = self.state_preparation.num_qubits - objective_qubits = self.objective_qubits + [num_qubits] - - # add the scaling qubit to the good state qualifier - def is_good_state(bitstr): - return self.is_good_state(bitstr[1:]) and bitstr[0] == "1" - - # rescaled estimation problem - problem = EstimationProblem( - rescaled_stateprep, - objective_qubits=objective_qubits, - post_processing=self.post_processing, - is_good_state=is_good_state, - ) - - return problem - - -def _rescale_amplitudes(circuit: QuantumCircuit, scaling_factor: float) -> QuantumCircuit: - r"""Uses an auxiliary qubit to scale the amplitude of :math:`|1\rangle` by ``scaling_factor``. - - Explained in Section 2.1. of [1]. - - For example, for a scaling factor of 0.25 this turns this circuit - - .. parsed-literal:: - - ┌───┐ - state_0: ─────┤ H ├─────────■──── - ┌───┴───┴───┐ ┌───┴───┐ - obj_0: ─┤ RY(0.125) ├─┤ RY(1) ├ - └───────────┘ └───────┘ - - into - - .. parsed-literal:: - - ┌───┐ - state_0: ─────┤ H ├─────────■──── - ┌───┴───┴───┐ ┌───┴───┐ - obj_0: ─┤ RY(0.125) ├─┤ RY(1) ├ - ┌┴───────────┴┐└───────┘ - scaling_0: ┤ RY(0.50536) ├───────── - └─────────────┘ - - References: - - [1]: K. Nakaji. Faster Amplitude Estimation, 2020; - `arXiv:2002.02417 `_ - - Args: - circuit: The circuit whose amplitudes to rescale. - scaling_factor: The rescaling factor. - - Returns: - A copy of the circuit with an additional qubit and RY gate for the rescaling. - """ - qr = QuantumRegister(1, "scaling") - rescaled = QuantumCircuit(*circuit.qregs, qr) - rescaled.compose(circuit, circuit.qubits, inplace=True) - rescaled.ry(2 * numpy.arcsin(scaling_factor), qr) - return rescaled diff --git a/qiskit/algorithms/amplitude_estimators/fae.py b/qiskit/algorithms/amplitude_estimators/fae.py deleted file mode 100644 index 08fedaffa34a..000000000000 --- a/qiskit/algorithms/amplitude_estimators/fae.py +++ /dev/null @@ -1,391 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Faster Amplitude Estimation.""" - -from __future__ import annotations -import warnings -import numpy as np - -from qiskit.circuit import QuantumCircuit, ClassicalRegister -from qiskit.providers import Backend -from qiskit.primitives import BaseSampler -from qiskit.utils import QuantumInstance -from qiskit.utils.deprecation import deprecate_arg, deprecate_func -from qiskit.algorithms.exceptions import AlgorithmError - -from .amplitude_estimator import AmplitudeEstimator, AmplitudeEstimatorResult -from .estimation_problem import EstimationProblem - - -class FasterAmplitudeEstimation(AmplitudeEstimator): - """The Faster Amplitude Estimation algorithm. - - The Faster Amplitude Estimation (FAE) [1] algorithm is a variant of Quantum Amplitude - Estimation (QAE), where the Quantum Phase Estimation (QPE) by an iterative Grover search, - similar to [2]. - - Due to the iterative version of the QPE, this algorithm does not require any additional - qubits, as the originally proposed QAE [3] and thus the resulting circuits are less complex. - - References: - - [1]: K. Nakaji. Faster Amplitude Estimation, 2020; - `arXiv:2002.02417 `_ - [2]: D. Grinko et al. Iterative Amplitude Estimation, 2019; - `arXiv:1912.05559 `_ - [3]: G. Brassard et al. Quantum Amplitude Amplification and Estimation, 2000; - `arXiv:quant-ph/0005055 `_ - - """ - - @deprecate_arg( - "quantum_instance", - additional_msg=( - "Instead, use the ``sampler`` argument. See https://qisk.it/algo_migration for a " - "migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - delta: float, - maxiter: int, - rescale: bool = True, - quantum_instance: QuantumInstance | Backend | None = None, - sampler: BaseSampler | None = None, - ) -> None: - r""" - Args: - delta: The probability that the true value is outside of the final confidence interval. - maxiter: The number of iterations, the maximal power of Q is `2 ** (maxiter - 1)`. - rescale: Whether to rescale the problem passed to `estimate`. - quantum_instance: Deprecated: The quantum instance or backend - to run the circuits. - sampler: A sampler primitive to evaluate the circuits. - - .. note:: - - This algorithm overwrites the number of shots set in the ``quantum_instance`` - argument, but will reset them to the initial number after running. - - """ - super().__init__() - # set quantum instance - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - self.quantum_instance = quantum_instance - self._shots = (int(1944 * np.log(2 / delta)), int(972 * np.log(2 / delta))) - self._rescale = rescale - self._delta = delta - self._maxiter = maxiter - self._num_oracle_calls = 0 - self._sampler = sampler - - @property - def sampler(self) -> BaseSampler | None: - """Get the sampler primitive. - - Returns: - The sampler primitive to evaluate the circuits. - """ - return self._sampler - - @sampler.setter - def sampler(self, sampler: BaseSampler) -> None: - """Set sampler primitive. - - Args: - sampler: A sampler primitive to evaluate the circuits. - """ - self._sampler = sampler - - @property - @deprecate_func( - since="0.24.0", - is_property=True, - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - ) - def quantum_instance(self) -> QuantumInstance | None: - """Deprecated. Get the quantum instance. - - Returns: - The quantum instance used to run this algorithm. - """ - return self._quantum_instance - - @quantum_instance.setter - @deprecate_func( - since="0.24.0", - is_property=True, - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - ) - def quantum_instance(self, quantum_instance: QuantumInstance | Backend) -> None: - """Deprecated. Set quantum instance. - - Args: - quantum_instance: The quantum instance used to run this algorithm. - """ - if isinstance(quantum_instance, Backend): - quantum_instance = QuantumInstance(quantum_instance) - self._quantum_instance = quantum_instance - - def _cos_estimate(self, estimation_problem, k, shots): - if self._quantum_instance is None and self._sampler is None: - raise ValueError("A quantum instance or sampler must be provided.") - - if self._sampler is not None: - circuit = self.construct_circuit(estimation_problem, k, measurement=True) - try: - job = self._sampler.run([circuit], shots=shots) - result = job.result() - except Exception as exc: - raise AlgorithmError("The job was not completed successfully. ") from exc - - if shots is None: - shots = 1 - self._num_oracle_calls += (2 * k + 1) * shots - - # sum over all probabilities where the objective qubits are 1 - prob = 0 - for bit, probabilities in result.quasi_dists[0].binary_probabilities().items(): - # check if it is a good state - if estimation_problem.is_good_state(bit): - prob += probabilities - - cos_estimate = 1 - 2 * prob - elif self._quantum_instance.is_statevector: - circuit = self.construct_circuit(estimation_problem, k, measurement=False) - statevector = self._quantum_instance.execute(circuit).get_statevector() - - # sum over all amplitudes where the objective qubits are 1 - prob = 0 - for i, amplitude in enumerate(statevector): - # get bitstring of objective qubits - full_state = bin(i)[2:].zfill(circuit.num_qubits)[::-1] - state = "".join([full_state[i] for i in estimation_problem.objective_qubits]) - - # check if it is a good state - if estimation_problem.is_good_state(state[::-1]): - prob = prob + np.abs(amplitude) ** 2 - - cos_estimate = 1 - 2 * prob - else: - circuit = self.construct_circuit(estimation_problem, k, measurement=True) - - self._quantum_instance.run_config.shots = shots - counts = self._quantum_instance.execute(circuit).get_counts() - self._num_oracle_calls += (2 * k + 1) * shots - - good_counts = 0 - for state, count in counts.items(): - if estimation_problem.is_good_state(state): - good_counts += count - - cos_estimate = 1 - 2 * good_counts / shots - - return cos_estimate - - def _chernoff(self, cos, shots) -> list[float]: - width = np.sqrt(np.log(2 / self._delta) * 12 / shots) - confint = [np.maximum(-1, cos - width), np.minimum(1, cos + width)] - return confint - - def construct_circuit( - self, estimation_problem: EstimationProblem, k: int, measurement: bool = False - ) -> QuantumCircuit | tuple[QuantumCircuit, list[int]]: - r"""Construct the circuit :math:`Q^k X |0\rangle>`. - - The A operator is the unitary specifying the QAE problem and Q the associated Grover - operator. - - Args: - estimation_problem: The estimation problem for which to construct the circuit. - k: The power of the Q operator. - measurement: Boolean flag to indicate if measurements should be included in the - circuits. - - Returns: - The circuit :math:`Q^k X |0\rangle`. - """ - num_qubits = max( - estimation_problem.state_preparation.num_qubits, - estimation_problem.grover_operator.num_qubits, - ) - circuit = QuantumCircuit(num_qubits, name="circuit") - - # add classical register if needed - if measurement: - c = ClassicalRegister(len(estimation_problem.objective_qubits)) - circuit.add_register(c) - - # add A operator - circuit.compose(estimation_problem.state_preparation, inplace=True) - - # add Q^k - if k != 0: - circuit.compose(estimation_problem.grover_operator.power(k), inplace=True) - - # add optional measurement - if measurement: - # real hardware can currently not handle operations after measurements, which might - # happen if the circuit gets transpiled, hence we're adding a safeguard-barrier - circuit.barrier() - circuit.measure(estimation_problem.objective_qubits, c[:]) - - return circuit - - def estimate(self, estimation_problem: EstimationProblem) -> "FasterAmplitudeEstimationResult": - """Run the amplitude estimation algorithm on provided estimation problem. - - Args: - estimation_problem: The estimation problem. - - Returns: - An amplitude estimation results object. - - Raises: - ValueError: A quantum instance or Sampler must be provided. - AlgorithmError: Sampler run error. - """ - if self._quantum_instance is None and self._sampler is None: - raise ValueError("A quantum instance or sampler must be provided.") - - self._num_oracle_calls = 0 - user_defined_shots = ( - self._quantum_instance._run_config.shots if self._quantum_instance is not None else None - ) - - if self._rescale: - problem = estimation_problem.rescale(0.25) - else: - problem = estimation_problem - - if self._quantum_instance is not None and self._quantum_instance.is_statevector: - cos = self._cos_estimate(problem, k=0, shots=1) - theta = np.arccos(cos) / 2 - theta_ci = [theta, theta] - theta_cis = [theta_ci] - num_steps = num_first_stage_steps = 1 - else: - theta_ci = [0, np.arcsin(0.25)] - first_stage = True - j_0 = self._maxiter - - theta_cis = [theta_ci] - num_first_stage_steps = 0 - num_steps = 0 - - def cos_estimate(power, shots): - return self._cos_estimate(problem, power, shots) - - for j in range(1, self._maxiter + 1): - num_steps += 1 - if first_stage: - num_first_stage_steps += 1 - c = cos_estimate(2 ** (j - 1), self._shots[0]) - chernoff_ci = self._chernoff(c, self._shots[0]) - theta_ci = [np.arccos(x) / (2 ** (j + 1) + 2) for x in chernoff_ci[::-1]] - - if 2 ** (j + 1) * theta_ci[1] >= 3 * np.pi / 8 and j < self._maxiter: - j_0 = j - v = 2**j * np.sum(theta_ci) - first_stage = False - else: - cos = cos_estimate(2 ** (j - 1), self._shots[1]) - cos_2 = cos_estimate(2 ** (j - 1) + 2 ** (j_0 - 1), self._shots[1]) - sin = (cos * np.cos(v) - cos_2) / np.sin(v) - rho = np.arctan2(sin, cos) - n = int(((2 ** (j + 1) + 2) * theta_ci[1] - rho + np.pi / 3) / (2 * np.pi)) - - theta_ci = [ - (2 * np.pi * n + rho + sign * np.pi / 3) / (2 ** (j + 1) + 2) - for sign in [-1, 1] - ] - theta_cis.append(theta_ci) - - theta = np.mean(theta_ci) - rescaling = 4 if self._rescale else 1 - value = (rescaling * np.sin(theta)) ** 2 - value_ci = ((rescaling * np.sin(theta_ci[0])) ** 2, (rescaling * np.sin(theta_ci[1])) ** 2) - - result = FasterAmplitudeEstimationResult() - result.num_oracle_queries = self._num_oracle_calls - result.num_steps = num_steps - result.num_first_state_steps = num_first_stage_steps - if self._quantum_instance is not None and self._quantum_instance.is_statevector: - result.success_probability = 1.0 - else: - result.success_probability = 1 - (2 * self._maxiter - j_0) * self._delta - - result.estimation = value - result.estimation_processed = problem.post_processing(value) - result.confidence_interval = value_ci - result.confidence_interval_processed = tuple(problem.post_processing(x) for x in value_ci) - result.theta_intervals = theta_cis - - # reset shots to what the user had defined - if self._quantum_instance is not None: - self._quantum_instance._run_config.shots = user_defined_shots - - return result - - -class FasterAmplitudeEstimationResult(AmplitudeEstimatorResult): - """The result object for the Faster Amplitude Estimation algorithm.""" - - def __init__(self) -> None: - super().__init__() - self._success_probability: float | None = None - self._num_steps: int | None = None - self._num_first_state_steps: int | None = None - self._theta_intervals: list[list[float]] | None = None - - @property - def success_probability(self) -> float: - """Return the success probability of the algorithm.""" - return self._success_probability - - @success_probability.setter - def success_probability(self, probability: float) -> None: - """Set the success probability of the algorithm.""" - self._success_probability = probability - - @property - def num_steps(self) -> int: - """Return the total number of steps taken in the algorithm.""" - return self._num_steps - - @num_steps.setter - def num_steps(self, num_steps: int) -> None: - """Set the total number of steps taken in the algorithm.""" - self._num_steps = num_steps - - @property - def num_first_state_steps(self) -> int: - """Return the number of steps taken in the first step of algorithm.""" - return self._num_first_state_steps - - @num_first_state_steps.setter - def num_first_state_steps(self, num_steps: int) -> None: - """Set the number of steps taken in the first step of algorithm.""" - self._num_first_state_steps = num_steps - - @property - def theta_intervals(self) -> list[list[float]]: - """Return the confidence intervals for the angles in each iteration.""" - return self._theta_intervals - - @theta_intervals.setter - def theta_intervals(self, value: list[list[float]]) -> None: - """Set the confidence intervals for the angles in each iteration.""" - self._theta_intervals = value diff --git a/qiskit/algorithms/amplitude_estimators/iae.py b/qiskit/algorithms/amplitude_estimators/iae.py deleted file mode 100644 index e420b76e6a4e..000000000000 --- a/qiskit/algorithms/amplitude_estimators/iae.py +++ /dev/null @@ -1,684 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Iterative Quantum Amplitude Estimation Algorithm.""" - -from __future__ import annotations -from typing import cast -import warnings -import numpy as np -from scipy.stats import beta - -from qiskit import ClassicalRegister, QuantumCircuit -from qiskit.providers import Backend -from qiskit.primitives import BaseSampler -from qiskit.utils import QuantumInstance -from qiskit.utils.deprecation import deprecate_arg, deprecate_func - -from .amplitude_estimator import AmplitudeEstimator, AmplitudeEstimatorResult -from .estimation_problem import EstimationProblem -from ..exceptions import AlgorithmError - - -class IterativeAmplitudeEstimation(AmplitudeEstimator): - r"""The Iterative Amplitude Estimation algorithm. - - This class implements the Iterative Quantum Amplitude Estimation (IQAE) algorithm, proposed - in [1]. The output of the algorithm is an estimate that, - with at least probability :math:`1 - \alpha`, differs by epsilon to the target value, where - both alpha and epsilon can be specified. - - It differs from the original QAE algorithm proposed by Brassard [2] in that it does not rely on - Quantum Phase Estimation, but is only based on Grover's algorithm. IQAE iteratively - applies carefully selected Grover iterations to find an estimate for the target amplitude. - - References: - [1]: Grinko, D., Gacon, J., Zoufal, C., & Woerner, S. (2019). - Iterative Quantum Amplitude Estimation. - `arXiv:1912.05559 `_. - [2]: Brassard, G., Hoyer, P., Mosca, M., & Tapp, A. (2000). - Quantum Amplitude Amplification and Estimation. - `arXiv:quant-ph/0005055 `_. - """ - - @deprecate_arg( - "quantum_instance", - additional_msg=( - "Instead, use the ``sampler`` argument. See https://qisk.it/algo_migration for a " - "migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - epsilon_target: float, - alpha: float, - confint_method: str = "beta", - min_ratio: float = 2, - quantum_instance: QuantumInstance | Backend | None = None, - sampler: BaseSampler | None = None, - ) -> None: - r""" - The output of the algorithm is an estimate for the amplitude `a`, that with at least - probability 1 - alpha has an error of epsilon. The number of A operator calls scales - linearly in 1/epsilon (up to a logarithmic factor). - - Args: - epsilon_target: Target precision for estimation target `a`, has values between 0 and 0.5 - alpha: Confidence level, the target probability is 1 - alpha, has values between 0 and 1 - confint_method: Statistical method used to estimate the confidence intervals in - each iteration, can be 'chernoff' for the Chernoff intervals or 'beta' for the - Clopper-Pearson intervals (default) - min_ratio: Minimal q-ratio (:math:`K_{i+1} / K_i`) for FindNextK - quantum_instance: Deprecated: Quantum Instance or Backend - sampler: A sampler primitive to evaluate the circuits. - - Raises: - AlgorithmError: if the method to compute the confidence intervals is not supported - ValueError: If the target epsilon is not in (0, 0.5] - ValueError: If alpha is not in (0, 1) - ValueError: If confint_method is not supported - """ - # validate ranges of input arguments - if not 0 < epsilon_target <= 0.5: - raise ValueError(f"The target epsilon must be in (0, 0.5], but is {epsilon_target}.") - - if not 0 < alpha < 1: - raise ValueError(f"The confidence level alpha must be in (0, 1), but is {alpha}") - - if confint_method not in {"chernoff", "beta"}: - raise ValueError( - f"The confidence interval method must be chernoff or beta, but is {confint_method}." - ) - - super().__init__() - - # set quantum instance - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - self.quantum_instance = quantum_instance - - # store parameters - self._epsilon = epsilon_target - self._alpha = alpha - self._min_ratio = min_ratio - self._confint_method = confint_method - self._sampler = sampler - - @property - def sampler(self) -> BaseSampler | None: - """Get the sampler primitive. - - Returns: - The sampler primitive to evaluate the circuits. - """ - return self._sampler - - @sampler.setter - def sampler(self, sampler: BaseSampler) -> None: - """Set sampler primitive. - - Args: - sampler: A sampler primitive to evaluate the circuits. - """ - self._sampler = sampler - - @property - @deprecate_func( - since="0.24.0", - is_property=True, - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - ) - def quantum_instance(self) -> QuantumInstance | None: - """Deprecated. Get the quantum instance. - - Returns: - The quantum instance used to run this algorithm. - """ - return self._quantum_instance - - @quantum_instance.setter - @deprecate_func( - since="0.24.0", - is_property=True, - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - ) - def quantum_instance(self, quantum_instance: QuantumInstance | Backend) -> None: - """Deprecated. Set quantum instance. - - Args: - quantum_instance: The quantum instance used to run this algorithm. - """ - if isinstance(quantum_instance, Backend): - quantum_instance = QuantumInstance(quantum_instance) - self._quantum_instance = quantum_instance - - @property - def epsilon_target(self) -> float: - """Returns the target precision ``epsilon_target`` of the algorithm. - - Returns: - The target precision (which is half the width of the confidence interval). - """ - return self._epsilon - - @epsilon_target.setter - def epsilon_target(self, epsilon: float) -> None: - """Set the target precision of the algorithm. - - Args: - epsilon: Target precision for estimation target `a`. - """ - self._epsilon = epsilon - - def _find_next_k( - self, - k: int, - upper_half_circle: bool, - theta_interval: tuple[float, float], - min_ratio: float = 2.0, - ) -> tuple[int, bool]: - """Find the largest integer k_next, such that the interval (4 * k_next + 2)*theta_interval - lies completely in [0, pi] or [pi, 2pi], for theta_interval = (theta_lower, theta_upper). - - Args: - k: The current power of the Q operator. - upper_half_circle: Boolean flag of whether theta_interval lies in the - upper half-circle [0, pi] or in the lower one [pi, 2pi]. - theta_interval: The current confidence interval for the angle theta, - i.e. (theta_lower, theta_upper). - min_ratio: Minimal ratio K/K_next allowed in the algorithm. - - Returns: - The next power k, and boolean flag for the extrapolated interval. - - Raises: - AlgorithmError: if min_ratio is smaller or equal to 1 - """ - if min_ratio <= 1: - raise AlgorithmError("min_ratio must be larger than 1 to ensure convergence") - - # initialize variables - theta_l, theta_u = theta_interval - old_scaling = 4 * k + 2 # current scaling factor, called K := (4k + 2) - - # the largest feasible scaling factor K cannot be larger than K_max, - # which is bounded by the length of the current confidence interval - max_scaling = int(1 / (2 * (theta_u - theta_l))) - scaling = max_scaling - (max_scaling - 2) % 4 # bring into the form 4 * k_max + 2 - - # find the largest feasible scaling factor K_next, and thus k_next - while scaling >= min_ratio * old_scaling: - theta_min = scaling * theta_l - int(scaling * theta_l) - theta_max = scaling * theta_u - int(scaling * theta_u) - - if theta_min <= theta_max <= 0.5 and theta_min <= 0.5: - # the extrapolated theta interval is in the upper half-circle - upper_half_circle = True - return int((scaling - 2) / 4), upper_half_circle - - elif theta_max >= 0.5 and theta_max >= theta_min >= 0.5: - # the extrapolated theta interval is in the upper half-circle - upper_half_circle = False - return int((scaling - 2) / 4), upper_half_circle - - scaling -= 4 - - # if we do not find a feasible k, return the old one - return int(k), upper_half_circle - - def construct_circuit( - self, estimation_problem: EstimationProblem, k: int = 0, measurement: bool = False - ) -> QuantumCircuit: - r"""Construct the circuit :math:`\mathcal{Q}^k \mathcal{A} |0\rangle`. - - The A operator is the unitary specifying the QAE problem and Q the associated Grover - operator. - - Args: - estimation_problem: The estimation problem for which to construct the QAE circuit. - k: The power of the Q operator. - measurement: Boolean flag to indicate if measurements should be included in the - circuits. - - Returns: - The circuit implementing :math:`\mathcal{Q}^k \mathcal{A} |0\rangle`. - """ - num_qubits = max( - estimation_problem.state_preparation.num_qubits, - estimation_problem.grover_operator.num_qubits, - ) - circuit = QuantumCircuit(num_qubits, name="circuit") - - # add classical register if needed - if measurement: - c = ClassicalRegister(len(estimation_problem.objective_qubits)) - circuit.add_register(c) - - # add A operator - circuit.compose(estimation_problem.state_preparation, inplace=True) - - # add Q^k - if k != 0: - circuit.compose(estimation_problem.grover_operator.power(k), inplace=True) - - # add optional measurement - if measurement: - # real hardware can currently not handle operations after measurements, which might - # happen if the circuit gets transpiled, hence we're adding a safeguard-barrier - circuit.barrier() - circuit.measure(estimation_problem.objective_qubits, c[:]) - - return circuit - - def _good_state_probability( - self, - problem: EstimationProblem, - counts_or_statevector: dict[str, int] | np.ndarray, - num_state_qubits: int, - ) -> tuple[int, float] | float: - """Get the probability to measure '1' in the last qubit. - - Args: - problem: The estimation problem, used to obtain the number of objective qubits and - the ``is_good_state`` function. - counts_or_statevector: Either a counts-dictionary (with one measured qubit only!) or - the statevector returned from the statevector_simulator. - num_state_qubits: The number of state qubits. - - Returns: - If a dict is given, return (#one-counts, #one-counts/#all-counts), - otherwise Pr(measure '1' in the last qubit). - """ - if isinstance(counts_or_statevector, dict): - one_counts = 0 - for state, counts in counts_or_statevector.items(): - if problem.is_good_state(state): - one_counts += counts - - return int(one_counts), one_counts / sum(counts_or_statevector.values()) - else: - statevector = counts_or_statevector - num_qubits = int(np.log2(len(statevector))) # the total number of qubits - - # sum over all amplitudes where the objective qubit is 1 - prob = 0 - for i, amplitude in enumerate(statevector): - # consider only state qubits and revert bit order - bitstr = bin(i)[2:].zfill(num_qubits)[-num_state_qubits:][::-1] - objectives = [bitstr[index] for index in problem.objective_qubits] - if problem.is_good_state(objectives): - prob = prob + np.abs(amplitude) ** 2 - - return prob - - def estimate( - self, estimation_problem: EstimationProblem - ) -> "IterativeAmplitudeEstimationResult": - """Run the amplitude estimation algorithm on provided estimation problem. - - Args: - estimation_problem: The estimation problem. - - Returns: - An amplitude estimation results object. - - Raises: - ValueError: A quantum instance or Sampler must be provided. - AlgorithmError: Sampler job run error. - """ - if self._quantum_instance is None and self._sampler is None: - raise ValueError("A quantum instance or sampler must be provided.") - - # initialize memory variables - powers = [0] # list of powers k: Q^k, (called 'k' in paper) - ratios = [] # list of multiplication factors (called 'q' in paper) - theta_intervals = [[0, 1 / 4]] # a priori knowledge of theta / 2 / pi - a_intervals = [[0.0, 1.0]] # a priori knowledge of the confidence interval of the estimate - num_oracle_queries = 0 - num_one_shots = [] - - # maximum number of rounds - max_rounds = ( - int(np.log(self._min_ratio * np.pi / 8 / self._epsilon) / np.log(self._min_ratio)) + 1 - ) - upper_half_circle = True # initially theta is in the upper half-circle - - if self._quantum_instance is not None and self._quantum_instance.is_statevector: - # for statevector we can directly return the probability to measure 1 - # note, that no iterations here are necessary - # simulate circuit - circuit = self.construct_circuit(estimation_problem, k=0, measurement=False) - ret = self._quantum_instance.execute(circuit) - - # get statevector - statevector = ret.get_statevector(circuit) - - # calculate the probability of measuring '1' - num_qubits = circuit.num_qubits - circuit.num_ancillas - prob = self._good_state_probability(estimation_problem, statevector, num_qubits) - prob = cast(float, prob) # tell MyPy it's a float and not Tuple[int, float ] - - a_confidence_interval = [prob, prob] # type: list[float] - a_intervals.append(a_confidence_interval) - - theta_i_interval = [ - np.arccos(1 - 2 * a_i) / 2 / np.pi for a_i in a_confidence_interval # type: ignore - ] - theta_intervals.append(theta_i_interval) - num_oracle_queries = 0 # no Q-oracle call, only a single one to A - - else: - num_iterations = 0 # keep track of the number of iterations - # number of shots per iteration - shots = 0 - # do while loop, keep in mind that we scaled theta mod 2pi such that it lies in [0,1] - while theta_intervals[-1][1] - theta_intervals[-1][0] > self._epsilon / np.pi: - num_iterations += 1 - - # get the next k - k, upper_half_circle = self._find_next_k( - powers[-1], - upper_half_circle, - theta_intervals[-1], # type: ignore - min_ratio=self._min_ratio, - ) - - # store the variables - powers.append(k) - ratios.append((2 * powers[-1] + 1) / (2 * powers[-2] + 1)) - - # run measurements for Q^k A|0> circuit - circuit = self.construct_circuit(estimation_problem, k, measurement=True) - counts = {} - if self._quantum_instance is not None: - ret = self._quantum_instance.execute(circuit) - # get the counts and store them - counts = ret.get_counts(circuit) - shots = self._quantum_instance._run_config.shots - else: - try: - job = self._sampler.run([circuit]) - ret = job.result() - except Exception as exc: - raise AlgorithmError("The job was not completed successfully. ") from exc - - shots = ret.metadata[0].get("shots") - if shots is None: - circuit = self.construct_circuit(estimation_problem, k=0, measurement=True) - try: - job = self._sampler.run([circuit]) - ret = job.result() - except Exception as exc: - raise AlgorithmError( - "The job was not completed successfully. " - ) from exc - - # calculate the probability of measuring '1' - prob = 0.0 - for bit, probabilities in ret.quasi_dists[0].binary_probabilities().items(): - # check if it is a good state - if estimation_problem.is_good_state(bit): - prob += probabilities - - a_confidence_interval = [prob, prob] - a_intervals.append(a_confidence_interval) - - theta_i_interval = [ - np.arccos(1 - 2 * a_i) / 2 / np.pi for a_i in a_confidence_interval - ] - theta_intervals.append(theta_i_interval) - num_oracle_queries = 0 # no Q-oracle call, only a single one to A - break - - counts = { - k: round(v * shots) - for k, v in ret.quasi_dists[0].binary_probabilities().items() - } - - # calculate the probability of measuring '1', 'prob' is a_i in the paper - num_qubits = circuit.num_qubits - circuit.num_ancillas - # type: ignore - one_counts, prob = self._good_state_probability( - estimation_problem, counts, num_qubits - ) - - num_one_shots.append(one_counts) - - # track number of Q-oracle calls - num_oracle_queries += shots * k - - # if on the previous iterations we have K_{i-1} == K_i, we sum these samples up - j = 1 # number of times we stayed fixed at the same K - round_shots = shots - round_one_counts = one_counts - if num_iterations > 1: - while ( - powers[num_iterations - j] == powers[num_iterations] - and num_iterations >= j + 1 - ): - j = j + 1 - round_shots += shots - round_one_counts += num_one_shots[-j] - - # compute a_min_i, a_max_i - if self._confint_method == "chernoff": - a_i_min, a_i_max = _chernoff_confint(prob, round_shots, max_rounds, self._alpha) - else: # 'beta' - a_i_min, a_i_max = _clopper_pearson_confint( - round_one_counts, round_shots, self._alpha / max_rounds - ) - - # compute theta_min_i, theta_max_i - if upper_half_circle: - theta_min_i = np.arccos(1 - 2 * a_i_min) / 2 / np.pi - theta_max_i = np.arccos(1 - 2 * a_i_max) / 2 / np.pi - else: - theta_min_i = 1 - np.arccos(1 - 2 * a_i_max) / 2 / np.pi - theta_max_i = 1 - np.arccos(1 - 2 * a_i_min) / 2 / np.pi - - # compute theta_u, theta_l of this iteration - scaling = 4 * k + 2 # current K_i factor - theta_u = (int(scaling * theta_intervals[-1][1]) + theta_max_i) / scaling - theta_l = (int(scaling * theta_intervals[-1][0]) + theta_min_i) / scaling - theta_intervals.append([theta_l, theta_u]) - - # compute a_u_i, a_l_i - a_u = np.sin(2 * np.pi * theta_u) ** 2 - a_l = np.sin(2 * np.pi * theta_l) ** 2 - a_u = cast(float, a_u) - a_l = cast(float, a_l) - a_intervals.append([a_l, a_u]) - - # get the latest confidence interval for the estimate of a - confidence_interval = tuple(a_intervals[-1]) - - # the final estimate is the mean of the confidence interval - estimation = np.mean(confidence_interval) - - result = IterativeAmplitudeEstimationResult() - result.alpha = self._alpha - result.post_processing = estimation_problem.post_processing - result.num_oracle_queries = num_oracle_queries - - result.estimation = estimation - result.epsilon_estimated = (confidence_interval[1] - confidence_interval[0]) / 2 - result.confidence_interval = confidence_interval - - result.estimation_processed = estimation_problem.post_processing(estimation) - confidence_interval = tuple( - estimation_problem.post_processing(x) for x in confidence_interval - ) - result.confidence_interval_processed = confidence_interval - result.epsilon_estimated_processed = (confidence_interval[1] - confidence_interval[0]) / 2 - result.estimate_intervals = a_intervals - result.theta_intervals = theta_intervals - result.powers = powers - result.ratios = ratios - - return result - - -class IterativeAmplitudeEstimationResult(AmplitudeEstimatorResult): - """The ``IterativeAmplitudeEstimation`` result object.""" - - def __init__(self) -> None: - super().__init__() - self._alpha: float | None = None - self._epsilon_target: float | None = None - self._epsilon_estimated: float | None = None - self._epsilon_estimated_processed: float | None = None - self._estimate_intervals: list[list[float]] | None = None - self._theta_intervals: list[list[float]] | None = None - self._powers: list[int] | None = None - self._ratios: list[float] | None = None - self._confidence_interval_processed: tuple[float, float] | None = None - - @property - def alpha(self) -> float: - r"""Return the confidence level :math:`\alpha`.""" - return self._alpha - - @alpha.setter - def alpha(self, value: float) -> None: - r"""Set the confidence level :math:`\alpha`.""" - self._alpha = value - - @property - def epsilon_target(self) -> float: - """Return the target half-width of the confidence interval.""" - return self._epsilon_target - - @epsilon_target.setter - def epsilon_target(self, value: float) -> None: - """Set the target half-width of the confidence interval.""" - self._epsilon_target = value - - @property - def epsilon_estimated(self) -> float: - """Return the estimated half-width of the confidence interval.""" - return self._epsilon_estimated - - @epsilon_estimated.setter - def epsilon_estimated(self, value: float) -> None: - """Set the estimated half-width of the confidence interval.""" - self._epsilon_estimated = value - - @property - def epsilon_estimated_processed(self) -> float: - """Return the post-processed estimated half-width of the confidence interval.""" - return self._epsilon_estimated_processed - - @epsilon_estimated_processed.setter - def epsilon_estimated_processed(self, value: float) -> None: - """Set the post-processed estimated half-width of the confidence interval.""" - self._epsilon_estimated_processed = value - - @property - def estimate_intervals(self) -> list[list[float]]: - """Return the confidence intervals for the estimate in each iteration.""" - return self._estimate_intervals - - @estimate_intervals.setter - def estimate_intervals(self, value: list[list[float]]) -> None: - """Set the confidence intervals for the estimate in each iteration.""" - self._estimate_intervals = value - - @property - def theta_intervals(self) -> list[list[float]]: - """Return the confidence intervals for the angles in each iteration.""" - return self._theta_intervals - - @theta_intervals.setter - def theta_intervals(self, value: list[list[float]]) -> None: - """Set the confidence intervals for the angles in each iteration.""" - self._theta_intervals = value - - @property - def powers(self) -> list[int]: - """Return the powers of the Grover operator in each iteration.""" - return self._powers - - @powers.setter - def powers(self, value: list[int]) -> None: - """Set the powers of the Grover operator in each iteration.""" - self._powers = value - - @property - def ratios(self) -> list[float]: - r"""Return the ratios :math:`K_{i+1}/K_{i}` for each iteration :math:`i`.""" - return self._ratios - - @ratios.setter - def ratios(self, value: list[float]) -> None: - r"""Set the ratios :math:`K_{i+1}/K_{i}` for each iteration :math:`i`.""" - self._ratios = value - - @property - def confidence_interval_processed(self) -> tuple[float, float]: - """Return the post-processed confidence interval.""" - return self._confidence_interval_processed - - @confidence_interval_processed.setter - def confidence_interval_processed(self, value: tuple[float, float]) -> None: - """Set the post-processed confidence interval.""" - self._confidence_interval_processed = value - - -def _chernoff_confint( - value: float, shots: int, max_rounds: int, alpha: float -) -> tuple[float, float]: - """Compute the Chernoff confidence interval for `shots` i.i.d. Bernoulli trials. - - The confidence interval is - - [value - eps, value + eps], where eps = sqrt(3 * log(2 * max_rounds/ alpha) / shots) - - but at most [0, 1]. - - Args: - value: The current estimate. - shots: The number of shots. - max_rounds: The maximum number of rounds, used to compute epsilon_a. - alpha: The confidence level, used to compute epsilon_a. - - Returns: - The Chernoff confidence interval. - """ - eps = np.sqrt(3 * np.log(2 * max_rounds / alpha) / shots) - lower = np.maximum(0, value - eps) - upper = np.minimum(1, value + eps) - return lower, upper - - -def _clopper_pearson_confint(counts: int, shots: int, alpha: float) -> tuple[float, float]: - """Compute the Clopper-Pearson confidence interval for `shots` i.i.d. Bernoulli trials. - - Args: - counts: The number of positive counts. - shots: The number of shots. - alpha: The confidence level for the confidence interval. - - Returns: - The Clopper-Pearson confidence interval. - """ - lower, upper = 0, 1 - - # if counts == 0, the beta quantile returns nan - if counts != 0: - lower = beta.ppf(alpha / 2, counts, shots - counts + 1) - - # if counts == shots, the beta quantile returns nan - if counts != shots: - upper = beta.ppf(1 - alpha / 2, counts + 1, shots - counts) - - return lower, upper diff --git a/qiskit/algorithms/amplitude_estimators/mlae.py b/qiskit/algorithms/amplitude_estimators/mlae.py deleted file mode 100644 index aa9f600b700e..000000000000 --- a/qiskit/algorithms/amplitude_estimators/mlae.py +++ /dev/null @@ -1,667 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Maximum Likelihood Amplitude Estimation algorithm.""" - -from __future__ import annotations -import warnings -from collections.abc import Sequence -from typing import Callable, List, Tuple - -import numpy as np -from scipy.optimize import brute -from scipy.stats import norm, chi2 - -from qiskit.providers import Backend -from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit -from qiskit.utils import QuantumInstance -from qiskit.primitives import BaseSampler -from qiskit.utils.deprecation import deprecate_arg, deprecate_func - -from .amplitude_estimator import AmplitudeEstimator, AmplitudeEstimatorResult -from .estimation_problem import EstimationProblem -from ..exceptions import AlgorithmError - -MINIMIZER = Callable[[Callable[[float], float], List[Tuple[float, float]]], float] - - -class MaximumLikelihoodAmplitudeEstimation(AmplitudeEstimator): - """The Maximum Likelihood Amplitude Estimation algorithm. - - This class implements the quantum amplitude estimation (QAE) algorithm without phase - estimation, as introduced in [1]. In comparison to the original QAE algorithm [2], - this implementation relies solely on different powers of the Grover operator and does not - require additional evaluation qubits. - Finally, the estimate is determined via a maximum likelihood estimation, which is why this - class in named ``MaximumLikelihoodAmplitudeEstimation``. - - References: - [1]: Suzuki, Y., Uno, S., Raymond, R., Tanaka, T., Onodera, T., & Yamamoto, N. (2019). - Amplitude Estimation without Phase Estimation. - `arXiv:1904.10246 `_. - [2]: Brassard, G., Hoyer, P., Mosca, M., & Tapp, A. (2000). - Quantum Amplitude Amplification and Estimation. - `arXiv:quant-ph/0005055 `_. - """ - - @deprecate_arg( - "quantum_instance", - additional_msg=( - "Instead, use the ``sampler`` argument. See https://qisk.it/algo_migration for a " - "migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - evaluation_schedule: list[int] | int, - minimizer: MINIMIZER | None = None, - quantum_instance: QuantumInstance | Backend | None = None, - sampler: BaseSampler | None = None, - ) -> None: - r""" - Args: - evaluation_schedule: If a list, the powers applied to the Grover operator. The list - element must be non-negative. If a non-negative integer, an exponential schedule is - used where the highest power is 2 to the integer minus 1: - `[id, Q^2^0, ..., Q^2^(evaluation_schedule-1)]`. - minimizer: A minimizer used to find the minimum of the likelihood function. - Defaults to a brute search where the number of evaluation points is determined - according to ``evaluation_schedule``. The minimizer takes a function as first - argument and a list of (float, float) tuples (as bounds) as second argument and - returns a single float which is the found minimum. - quantum_instance: Deprecated: Quantum Instance or Backend - sampler: A sampler primitive to evaluate the circuits. - - Raises: - ValueError: If the number of oracle circuits is smaller than 1. - """ - - super().__init__() - - # set quantum instance - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - self.quantum_instance = quantum_instance - - # get parameters - if isinstance(evaluation_schedule, int): - if evaluation_schedule < 0: - raise ValueError("The evaluation schedule cannot be < 0.") - - self._evaluation_schedule = [0] + [2**j for j in range(evaluation_schedule)] - else: - if any(value < 0 for value in evaluation_schedule): - raise ValueError("The elements of the evaluation schedule cannot be < 0.") - - self._evaluation_schedule = evaluation_schedule - - if minimizer is None: - # default number of evaluations is max(10^4, pi/2 * 10^3 * 2^(m)) - nevals = max(10000, int(np.pi / 2 * 1000 * 2 * self._evaluation_schedule[-1])) - - def default_minimizer(objective_fn, bounds): - return brute(objective_fn, bounds, Ns=nevals)[0] - - self._minimizer = default_minimizer - else: - self._minimizer = minimizer - - self._sampler = sampler - - @property - def sampler(self) -> BaseSampler | None: - """Get the sampler primitive. - - Returns: - The sampler primitive to evaluate the circuits. - """ - return self._sampler - - @sampler.setter - def sampler(self, sampler: BaseSampler) -> None: - """Set sampler primitive. - - Args: - sampler: A sampler primitive to evaluate the circuits. - """ - self._sampler = sampler - - @property - @deprecate_func( - since="0.24.0", - is_property=True, - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - ) - def quantum_instance(self) -> QuantumInstance | None: - """Deprecated. Get the quantum instance. - - Returns: - The quantum instance used to run this algorithm. - """ - return self._quantum_instance - - @quantum_instance.setter - @deprecate_func( - since="0.24.0", - is_property=True, - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - ) - def quantum_instance(self, quantum_instance: QuantumInstance | Backend) -> None: - """Deprecated. Set quantum instance. - - Args: - quantum_instance: The quantum instance used to run this algorithm. - """ - if isinstance(quantum_instance, Backend): - quantum_instance = QuantumInstance(quantum_instance) - self._quantum_instance = quantum_instance - - def construct_circuits( - self, estimation_problem: EstimationProblem, measurement: bool = False - ) -> list[QuantumCircuit]: - """Construct the Amplitude Estimation w/o QPE quantum circuits. - - Args: - estimation_problem: The estimation problem for which to construct the QAE circuit. - measurement: Boolean flag to indicate if measurement should be included in the circuits. - - Returns: - A list with the QuantumCircuit objects for the algorithm. - """ - # keep track of the Q-oracle queries - circuits = [] - - num_qubits = max( - estimation_problem.state_preparation.num_qubits, - estimation_problem.grover_operator.num_qubits, - ) - q = QuantumRegister(num_qubits, "q") - qc_0 = QuantumCircuit(q, name="qc_a") # 0 applications of Q, only a single A operator - - # add classical register if needed - if measurement: - c = ClassicalRegister(len(estimation_problem.objective_qubits)) - qc_0.add_register(c) - - qc_0.compose(estimation_problem.state_preparation, inplace=True) - - for k in self._evaluation_schedule: - qc_k = qc_0.copy(name=f"qc_a_q_{k}") - - if k != 0: - qc_k.compose(estimation_problem.grover_operator.power(k), inplace=True) - - if measurement: - # real hardware can currently not handle operations after measurements, - # which might happen if the circuit gets transpiled, hence we're adding - # a safeguard-barrier - qc_k.barrier() - qc_k.measure(estimation_problem.objective_qubits, c[:]) - - circuits += [qc_k] - - return circuits - - @staticmethod - def compute_confidence_interval( - result: "MaximumLikelihoodAmplitudeEstimationResult", - alpha: float, - kind: str = "fisher", - apply_post_processing: bool = False, - ) -> tuple[float, float]: - """Compute the `alpha` confidence interval using the method `kind`. - - The confidence level is (1 - `alpha`) and supported kinds are 'fisher', - 'likelihood_ratio' and 'observed_fisher' with shorthand - notations 'fi', 'lr' and 'oi', respectively. - - Args: - result: A maximum likelihood amplitude estimation result. - alpha: The confidence level. - kind: The method to compute the confidence interval. Defaults to 'fisher', which - computes the theoretical Fisher information. - apply_post_processing: If True, apply post-processing to the confidence interval. - - Returns: - The specified confidence interval. - - Raises: - AlgorithmError: If `run()` hasn't been called yet. - NotImplementedError: If the method `kind` is not supported. - """ - interval: tuple[float, float] | None = None - - # if statevector simulator the estimate is exact - if all(isinstance(data, (list, np.ndarray)) for data in result.circuit_results): - interval = (result.estimation, result.estimation) - - elif kind in ["likelihood_ratio", "lr"]: - interval = _likelihood_ratio_confint(result, alpha) - - elif kind in ["fisher", "fi"]: - interval = _fisher_confint(result, alpha, observed=False) - - elif kind in ["observed_fisher", "observed_information", "oi"]: - interval = _fisher_confint(result, alpha, observed=True) - - if interval is None: - raise NotImplementedError(f"CI `{kind}` is not implemented.") - - if apply_post_processing: - return result.post_processing(interval[0]), result.post_processing(interval[1]) - - return interval - - def compute_mle( - self, - circuit_results: list[dict[str, int] | np.ndarray], - estimation_problem: EstimationProblem, - num_state_qubits: int | None = None, - return_counts: bool = False, - ) -> float | tuple[float, list[float]]: - """Compute the MLE via a grid-search. - - This is a stable approach if sufficient gridpoints are used. - - Args: - circuit_results: A list of circuit outcomes. Can be counts or statevectors. - estimation_problem: The estimation problem containing the evaluation schedule and the - number of likelihood function evaluations used to find the minimum. - num_state_qubits: The number of state qubits, required for statevector simulations. - return_counts: If True, returns the good counts. - - Returns: - The MLE for the provided result object. - """ - good_counts, all_counts = _get_counts(circuit_results, estimation_problem, num_state_qubits) - - # search range - eps = 1e-15 # to avoid invalid value in log - search_range = [0 + eps, np.pi / 2 - eps] - - def loglikelihood(theta): - # loglik contains the first `it` terms of the full loglikelihood - loglik = 0 - for i, k in enumerate(self._evaluation_schedule): - angle = (2 * k + 1) * theta - loglik += np.log(np.sin(angle) ** 2) * good_counts[i] - loglik += np.log(np.cos(angle) ** 2) * (all_counts[i] - good_counts[i]) - return -loglik - - est_theta = self._minimizer(loglikelihood, [search_range]) - - if return_counts: - return est_theta, good_counts - return est_theta - - def estimate( - self, estimation_problem: EstimationProblem - ) -> "MaximumLikelihoodAmplitudeEstimationResult": - """Run the amplitude estimation algorithm on provided estimation problem. - - Args: - estimation_problem: The estimation problem. - - Returns: - An amplitude estimation results object. - - Raises: - ValueError: A quantum instance or Sampler must be provided. - AlgorithmError: If `state_preparation` is not set in - `estimation_problem`. - AlgorithmError: Sampler job run error - """ - if self._quantum_instance is None and self._sampler is None: - raise ValueError("A quantum instance or sampler must be provided.") - if estimation_problem.state_preparation is None: - raise AlgorithmError( - "The state_preparation property of the estimation problem must be set." - ) - - result = MaximumLikelihoodAmplitudeEstimationResult() - result.evaluation_schedule = self._evaluation_schedule - result.minimizer = self._minimizer - result.post_processing = estimation_problem.post_processing - - shots = 0 - if self._quantum_instance is not None and self._quantum_instance.is_statevector: - # run circuit on statevector simulator - circuits = self.construct_circuits(estimation_problem, measurement=False) - ret = self._quantum_instance.execute(circuits) - - # get statevectors and construct MLE input - statevectors = [np.asarray(ret.get_statevector(circuit)) for circuit in circuits] - result.circuit_results = statevectors - - # to count the number of Q-oracle calls (don't count shots) - result.shots = 1 - else: - circuits = self.construct_circuits(estimation_problem, measurement=True) - if self._quantum_instance is not None: - # run circuit on QASM simulator - ret = self._quantum_instance.execute(circuits) - # get counts and construct MLE input - result.circuit_results = [ret.get_counts(circuit) for circuit in circuits] - shots = self._quantum_instance._run_config.shots - else: - try: - job = self._sampler.run(circuits) - ret = job.result() - except Exception as exc: - raise AlgorithmError("The job was not completed successfully. ") from exc - - result.circuit_results = [] - shots = ret.metadata[0].get("shots") - if shots is None: - for quasi_dist in ret.quasi_dists: - circuit_result = quasi_dist.binary_probabilities() - result.circuit_results.append(circuit_result) - shots = 1 - else: - # get counts and construct MLE input - for quasi_dist in ret.quasi_dists: - counts = { - k: round(v * shots) - for k, v in quasi_dist.binary_probabilities().items() - } - result.circuit_results.append(counts) - - result.shots = shots - - # run maximum likelihood estimation - num_state_qubits = circuits[0].num_qubits - circuits[0].num_ancillas - theta, good_counts = self.compute_mle( - result.circuit_results, estimation_problem, num_state_qubits, True - ) - - # store results - result.theta = theta - result.good_counts = good_counts - result.estimation = np.sin(result.theta) ** 2 - - # not sure why pylint complains, this is a callable and the tests pass - # pylint: disable=not-callable - result.estimation_processed = result.post_processing(result.estimation) - - result.fisher_information = _compute_fisher_information(result) - result.num_oracle_queries = result.shots * sum(k for k in result.evaluation_schedule) - - # compute and store confidence interval - confidence_interval = self.compute_confidence_interval(result, alpha=0.05, kind="fisher") - result.confidence_interval = confidence_interval - result.confidence_interval_processed = tuple( - estimation_problem.post_processing(value) for value in confidence_interval - ) - - return result - - -class MaximumLikelihoodAmplitudeEstimationResult(AmplitudeEstimatorResult): - """The ``MaximumLikelihoodAmplitudeEstimation`` result object.""" - - def __init__(self) -> None: - super().__init__() - self._theta: float | None = None - self._minimizer: Callable | None = None - self._good_counts: list[float] | None = None - self._evaluation_schedule: list[int] | None = None - self._fisher_information: float | None = None - - @property - def theta(self) -> float: - r"""Return the estimate for the angle :math:`\theta`.""" - return self._theta - - @theta.setter - def theta(self, value: float) -> None: - r"""Set the estimate for the angle :math:`\theta`.""" - self._theta = value - - @property - def minimizer(self) -> Callable: - """Return the minimizer used for the search of the likelihood function.""" - return self._minimizer - - @minimizer.setter - def minimizer(self, value: Callable) -> None: - """Set the number minimizer used for the search of the likelihood function.""" - self._minimizer = value - - @property - def good_counts(self) -> list[float]: - """Return the percentage of good counts per circuit power.""" - return self._good_counts - - @good_counts.setter - def good_counts(self, counts: list[float]) -> None: - """Set the percentage of good counts per circuit power.""" - self._good_counts = counts - - @property - def evaluation_schedule(self) -> list[int]: - """Return the evaluation schedule for the powers of the Grover operator.""" - return self._evaluation_schedule - - @evaluation_schedule.setter - def evaluation_schedule(self, evaluation_schedule: list[int]) -> None: - """Set the evaluation schedule for the powers of the Grover operator.""" - self._evaluation_schedule = evaluation_schedule - - @property - def fisher_information(self) -> float: - """Return the Fisher information for the estimated amplitude.""" - return self._fisher_information - - @fisher_information.setter - def fisher_information(self, value: float) -> None: - """Set the Fisher information for the estimated amplitude.""" - self._fisher_information = value - - -def _safe_min(array, default=0): - if len(array) == 0: - return default - return np.min(array) - - -def _safe_max(array, default=(np.pi / 2)): - if len(array) == 0: - return default - return np.max(array) - - -def _compute_fisher_information( - result: "MaximumLikelihoodAmplitudeEstimationResult", - num_sum_terms: int | None = None, - observed: bool = False, -) -> float: - """Compute the Fisher information. - - Args: - result: A maximum likelihood amplitude estimation result. - num_sum_terms: The number of sum terms to be included in the calculation of the - Fisher information. By default all values are included. - observed: If True, compute the observed Fisher information, otherwise the theoretical - one. - - Returns: - The computed Fisher information, or np.inf if statevector simulation was used. - - Raises: - KeyError: Call run() first! - """ - a = result.estimation - - # Corresponding angle to the value a (only use real part of 'a') - theta_a = np.arcsin(np.sqrt(np.real(a))) - - # Get the number of hits (shots_k) and one-hits (h_k) - one_hits = result.good_counts - all_hits = [result.shots] * len(one_hits) - - # Include all sum terms or just up to a certain term? - evaluation_schedule = result.evaluation_schedule - if num_sum_terms is not None: - evaluation_schedule = evaluation_schedule[:num_sum_terms] - # not necessary since zip goes as far as shortest list: - # all_hits = all_hits[:num_sum_terms] - # one_hits = one_hits[:num_sum_terms] - - # Compute the Fisher information - fisher_information = None - if observed: - # Note, that the observed Fisher information is very unreliable in this algorithm! - d_loglik = 0 - for shots_k, h_k, m_k in zip(all_hits, one_hits, evaluation_schedule): - tan = np.tan((2 * m_k + 1) * theta_a) - d_loglik += (2 * m_k + 1) * (h_k / tan + (shots_k - h_k) * tan) - - d_loglik /= np.sqrt(a * (1 - a)) - fisher_information = d_loglik**2 / len(all_hits) - - else: - fisher_information = sum( - shots_k * (2 * m_k + 1) ** 2 for shots_k, m_k in zip(all_hits, evaluation_schedule) - ) - fisher_information /= a * (1 - a) - - return fisher_information - - -def _fisher_confint( - result: MaximumLikelihoodAmplitudeEstimationResult, alpha: float = 0.05, observed: bool = False -) -> tuple[float, float]: - """Compute the `alpha` confidence interval based on the Fisher information. - - Args: - result: A maximum likelihood amplitude estimation results object. - alpha: The level of the confidence interval (must be <= 0.5), default to 0.05. - observed: If True, use observed Fisher information. - - Returns: - float: The alpha confidence interval based on the Fisher information - Raises: - AssertionError: Call run() first! - """ - # Get the (observed) Fisher information - fisher_information = None - try: - fisher_information = result.fisher_information - except KeyError as ex: - raise AssertionError("Call run() first!") from ex - - if observed: - fisher_information = _compute_fisher_information(result, observed=True) - - normal_quantile = norm.ppf(1 - alpha / 2) - confint = np.real(result.estimation) + normal_quantile / np.sqrt(fisher_information) * np.array( - [-1, 1] - ) - return result.post_processing(confint[0]), result.post_processing(confint[1]) - - -def _likelihood_ratio_confint( - result: MaximumLikelihoodAmplitudeEstimationResult, - alpha: float = 0.05, - nevals: int | None = None, -) -> tuple[float, float]: - """Compute the likelihood-ratio confidence interval. - - Args: - result: A maximum likelihood amplitude estimation results object. - alpha: The level of the confidence interval (< 0.5), defaults to 0.05. - nevals: The number of evaluations to find the intersection with the loglikelihood - function. Defaults to an adaptive value based on the maximal power of Q. - - Returns: - The alpha-likelihood-ratio confidence interval. - """ - if nevals is None: - nevals = max(10000, int(np.pi / 2 * 1000 * 2 * result.evaluation_schedule[-1])) - - def loglikelihood(theta, one_counts, all_counts): - loglik = 0 - for i, k in enumerate(result.evaluation_schedule): - loglik += np.log(np.sin((2 * k + 1) * theta) ** 2) * one_counts[i] - loglik += np.log(np.cos((2 * k + 1) * theta) ** 2) * (all_counts[i] - one_counts[i]) - return loglik - - one_counts = result.good_counts - all_counts = [result.shots] * len(one_counts) - - eps = 1e-15 # to avoid invalid value in log - thetas = np.linspace(0 + eps, np.pi / 2 - eps, nevals) - values = np.zeros(len(thetas)) - for i, theta in enumerate(thetas): - values[i] = loglikelihood(theta, one_counts, all_counts) - - loglik_mle = loglikelihood(result.theta, one_counts, all_counts) - chi2_quantile = chi2.ppf(1 - alpha, df=1) - thres = loglik_mle - chi2_quantile / 2 - - # the (outer) LR confidence interval - above_thres = thetas[values >= thres] - - # it might happen that the `above_thres` array is empty, - # to still provide a valid result use safe_min/max which - # then yield [0, pi/2] - confint = [_safe_min(above_thres, default=0), _safe_max(above_thres, default=np.pi / 2)] - mapped_confint = tuple(result.post_processing(np.sin(bound) ** 2) for bound in confint) - - return mapped_confint - - -def _get_counts( - circuit_results: Sequence[np.ndarray | list[float] | dict[str, int]], - estimation_problem: EstimationProblem, - num_state_qubits: int, -) -> tuple[list[float], list[int]]: - """Get the good and total counts. - - Returns: - A pair of two lists, ([1-counts per experiment], [shots per experiment]). - - Raises: - AlgorithmError: If self.run() has not been called yet. - """ - one_hits = [] # h_k: how often 1 has been measured, for a power Q^(m_k) - # shots_k: how often has been measured at a power Q^(m_k) - all_hits: np.ndarray | list[float] = [] - if all(isinstance(data, (list, np.ndarray)) for data in circuit_results): - probabilities = [] - num_qubits = int(np.log2(len(circuit_results[0]))) # the total number of qubits - for statevector in circuit_results: - p_k = 0.0 - for i, amplitude in enumerate(statevector): - probability = np.abs(amplitude) ** 2 - # consider only state qubits and revert bit order - bitstr = bin(i)[2:].zfill(num_qubits)[-num_state_qubits:][::-1] - objectives = [bitstr[index] for index in estimation_problem.objective_qubits] - if estimation_problem.is_good_state(objectives): - p_k += probability - probabilities += [p_k] - - one_hits = probabilities - all_hits = np.ones_like(one_hits) - else: - for counts in circuit_results: - all_hits.append(sum(counts.values())) - one_hits.append( - sum( - count - for bitstr, count in counts.items() - if estimation_problem.is_good_state(bitstr) - ) - ) - - return one_hits, all_hits diff --git a/qiskit/algorithms/aux_ops_evaluator.py b/qiskit/algorithms/aux_ops_evaluator.py deleted file mode 100644 index 788b66d6e43f..000000000000 --- a/qiskit/algorithms/aux_ops_evaluator.py +++ /dev/null @@ -1,195 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""Evaluator of auxiliary operators for algorithms.""" - -from __future__ import annotations - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.opflow import ( - CircuitSampler, - ListOp, - StateFn, - OperatorBase, - ExpectationBase, -) -from qiskit.providers import Backend -from qiskit.quantum_info import Statevector -from qiskit.utils import QuantumInstance -from qiskit.utils.deprecation import deprecate_func - -from .list_or_dict import ListOrDict - - -@deprecate_func( - additional_msg=( - "Instead, use the function " - "``qiskit.algorithms.observables_evaluator.estimate_observables``. See " - "https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", -) -def eval_observables( - quantum_instance: QuantumInstance | Backend, - quantum_state: Statevector | QuantumCircuit | OperatorBase, - observables: ListOrDict[OperatorBase], - expectation: ExpectationBase, - threshold: float = 1e-12, -) -> ListOrDict[tuple[complex, complex]]: - """ - Deprecated: Accepts a list or a dictionary of operators and calculates - their expectation values - means - and standard deviations. They are calculated with respect to a quantum state provided. A user - can optionally provide a threshold value which filters mean values falling below the threshold. - - This function has been superseded by the - :func:`qiskit.algorithms.observables_evaluator.eval_observables` function. - It will be deprecated in a future release and subsequently - removed after that. - - Args: - quantum_instance: A quantum instance used for calculations. - quantum_state: An unparametrized quantum circuit representing a quantum state that - expectation values are computed against. - observables: A list or a dictionary of operators whose expectation values are to be - calculated. - expectation: An instance of ExpectationBase which defines a method for calculating - expectation values. - threshold: A threshold value that defines which mean values should be neglected (helpful for - ignoring numerical instabilities close to 0). - - Returns: - A list or a dictionary of tuples (mean, standard deviation). - - Raises: - ValueError: If a ``quantum_state`` with free parameters is provided. - """ - - if ( - isinstance( - quantum_state, (QuantumCircuit, OperatorBase) - ) # Statevector cannot be parametrized - and len(quantum_state.parameters) > 0 - ): - raise ValueError( - "A parametrized representation of a quantum_state was provided. It is not " - "allowed - it cannot have free parameters." - ) - - # Create new CircuitSampler to avoid breaking existing one's caches. - sampler = CircuitSampler(quantum_instance) - - list_op = _prepare_list_op(quantum_state, observables) - observables_expect = expectation.convert(list_op) - observables_expect_sampled = sampler.convert(observables_expect) - - # compute means - values = np.real(observables_expect_sampled.eval()) - - # compute standard deviations - # We use sampler.quantum_instance to take care of case in which quantum_instance is Backend - std_devs = _compute_std_devs( - observables_expect_sampled, observables, expectation, sampler.quantum_instance - ) - - # Discard values below threshold - observables_means = values * (np.abs(values) > threshold) - # zip means and standard deviations into tuples - observables_results = list(zip(observables_means, std_devs)) - - # Return None eigenvalues for None operators if observables is a list. - # None operators are already dropped in compute_minimum_eigenvalue if observables is a dict. - - return _prepare_result(observables_results, observables) - - -def _prepare_list_op( - quantum_state: Statevector | QuantumCircuit | OperatorBase, - observables: ListOrDict[OperatorBase], -) -> ListOp: - """ - Accepts a list or a dictionary of operators and converts them to a ``ListOp``. - - Args: - quantum_state: An unparametrized quantum circuit representing a quantum state that - expectation values are computed against. - observables: A list or a dictionary of operators. - - Returns: - A ``ListOp`` that includes all provided observables. - """ - if isinstance(observables, dict): - observables = list(observables.values()) - - if not isinstance(quantum_state, StateFn): - quantum_state = StateFn(quantum_state) - - return ListOp([StateFn(obs, is_measurement=True).compose(quantum_state) for obs in observables]) - - -def _prepare_result( - observables_results: list[tuple[complex, complex]], - observables: ListOrDict[OperatorBase], -) -> ListOrDict[tuple[complex, complex]]: - """ - Prepares a list or a dictionary of eigenvalues from ``observables_results`` and - ``observables``. - - Args: - observables_results: A list of of tuples (mean, standard deviation). - observables: A list or a dictionary of operators whose expectation values are to be - calculated. - - Returns: - A list or a dictionary of tuples (mean, standard deviation). - """ - if isinstance(observables, list): - observables_eigenvalues: ListOrDict[tuple[complex, complex]] = [None] * len(observables) - key_value_iterator = enumerate(observables_results) - else: - observables_eigenvalues = {} - key_value_iterator = zip(observables.keys(), observables_results) - for key, value in key_value_iterator: - if observables[key] is not None: - observables_eigenvalues[key] = value - return observables_eigenvalues - - -def _compute_std_devs( - observables_expect_sampled: OperatorBase, - observables: ListOrDict[OperatorBase], - expectation: ExpectationBase, - quantum_instance: QuantumInstance | Backend, -) -> list[complex]: - """ - Calculates a list of standard deviations from expectation values of observables provided. - - Args: - observables_expect_sampled: Expected values of observables. - observables: A list or a dictionary of operators whose expectation values are to be - calculated. - expectation: An instance of ExpectationBase which defines a method for calculating - expectation values. - quantum_instance: A quantum instance used for calculations. - - Returns: - A list of standard deviations. - """ - variances = np.real(expectation.compute_variance(observables_expect_sampled)) - if not isinstance(variances, np.ndarray) and variances == 0.0: - # when `variances` is a single value equal to 0., our expectation value is exact and we - # manually ensure the variances to be a list of the correct length - variances = np.zeros(len(observables), dtype=float) - # TODO: this will crash if quantum_instance is a backend - std_devs = np.sqrt(variances / quantum_instance.run_config.shots) - return std_devs diff --git a/qiskit/algorithms/eigen_solvers/__init__.py b/qiskit/algorithms/eigen_solvers/__init__.py deleted file mode 100644 index 90cab015b8f5..000000000000 --- a/qiskit/algorithms/eigen_solvers/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Eigen Solvers Package""" - -from .numpy_eigen_solver import NumPyEigensolver -from .eigen_solver import Eigensolver, EigensolverResult -from .vqd import VQD, VQDResult - -__all__ = ["NumPyEigensolver", "Eigensolver", "EigensolverResult", "VQD", "VQDResult"] diff --git a/qiskit/algorithms/eigen_solvers/eigen_solver.py b/qiskit/algorithms/eigen_solvers/eigen_solver.py deleted file mode 100644 index 5fd59b3e023c..000000000000 --- a/qiskit/algorithms/eigen_solvers/eigen_solver.py +++ /dev/null @@ -1,134 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Eigensolver interface""" -from __future__ import annotations -from abc import ABC, abstractmethod - -import numpy as np - -from qiskit.opflow import OperatorBase -from qiskit.utils.deprecation import deprecate_func -from ..algorithm_result import AlgorithmResult -from ..list_or_dict import ListOrDict - - -class Eigensolver(ABC): - """Deprecated: Eigensolver Interface. - - The Eigensolver interface has been superseded by the - :class:`qiskit.algorithms.eigensolvers.Eigensolver` interface. - This interface will be deprecated in a future release and subsequently - removed after that. - - Algorithms that can compute eigenvalues for an operator - may implement this interface to allow different algorithms to be - used interchangeably. - """ - - @deprecate_func( - additional_msg=( - "Instead, use the interface ``qiskit.algorithms.eigensolvers.Eigensolver``. See " - "https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__(self) -> None: - pass - - @abstractmethod - def compute_eigenvalues( - self, operator: OperatorBase, aux_operators: ListOrDict[OperatorBase] | None = None - ) -> "EigensolverResult": - """ - Computes eigenvalues. Operator and aux_operators can be supplied here and - if not None will override any already set into algorithm so it can be reused with - different operators. While an operator is required by algorithms, aux_operators - are optional. To 'remove' a previous aux_operators array use an empty list here. - - Args: - operator: Qubit operator of the Observable - aux_operators: Optional list of auxiliary operators to be evaluated with the - eigenstate of the minimum eigenvalue main result and their expectation values - returned. For instance in chemistry these can be dipole operators, total particle - count operators so we can get values for these at the ground state. - - Returns: - EigensolverResult - """ - return EigensolverResult() - - @classmethod - def supports_aux_operators(cls) -> bool: - """Whether computing the expectation value of auxiliary operators is supported. - - Returns: - True if aux_operator expectations can be evaluated, False otherwise - """ - return False - - -class EigensolverResult(AlgorithmResult): - """Deprecated: Eigensolver Result. - - The EigensolverResult class has been superseded by the - :class:`qiskit.algorithms.eigensolvers.EigensolverResult` class. - This class will be deprecated in a future release and subsequently - removed after that. - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.eigensolvers.EigensolverResult``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__(self) -> None: - super().__init__() - self._eigenvalues: np.ndarray | None = None - self._eigenstates: np.ndarray | None = None - self._aux_operator_eigenvalues: list[ListOrDict[tuple[complex, complex]]] | None = None - - @property - def eigenvalues(self) -> np.ndarray | None: - """returns eigen values""" - return self._eigenvalues - - @eigenvalues.setter - def eigenvalues(self, value: np.ndarray) -> None: - """set eigen values""" - self._eigenvalues = value - - @property - def eigenstates(self) -> np.ndarray | None: - """return eigen states""" - return self._eigenstates - - @eigenstates.setter - def eigenstates(self, value: np.ndarray) -> None: - """set eigen states""" - self._eigenstates = value - - @property - def aux_operator_eigenvalues(self) -> list[ListOrDict[tuple[complex, complex]]] | None: - """Return aux operator expectation values. - - These values are in fact tuples formatted as (mean, standard deviation). - """ - return self._aux_operator_eigenvalues - - @aux_operator_eigenvalues.setter - def aux_operator_eigenvalues(self, value: list[ListOrDict[tuple[complex, complex]]]) -> None: - """set aux operator eigen values""" - self._aux_operator_eigenvalues = value diff --git a/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py b/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py deleted file mode 100644 index 6b0536330441..000000000000 --- a/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py +++ /dev/null @@ -1,278 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Eigensolver algorithm.""" -from __future__ import annotations - -import logging -import warnings -from collections.abc import Callable - -import numpy as np -from scipy import sparse as scisparse - -from qiskit.opflow import I, ListOp, OperatorBase, StateFn -from qiskit.utils.validation import validate_min -from qiskit.utils.deprecation import deprecate_func -from ..exceptions import AlgorithmError -from .eigen_solver import Eigensolver, EigensolverResult -from ..list_or_dict import ListOrDict - -logger = logging.getLogger(__name__) - - -class NumPyEigensolver(Eigensolver): - r""" - Deprecated: NumPy Eigensolver algorithm. - - The NumPyEigensolver class has been superseded by the - :class:`qiskit.algorithms.eigensolvers.NumPyEigensolver` class. - This class will be deprecated in a future release and subsequently - removed after that. - - NumPy Eigensolver computes up to the first :math:`k` eigenvalues of a complex-valued square - matrix of dimension :math:`n \times n`, with :math:`k \leq n`. - - Note: - Operators are automatically converted to SciPy's ``spmatrix`` - as needed and this conversion can be costly in terms of memory and performance as the - operator size, mostly in terms of number of qubits it represents, gets larger. - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.eigensolvers.NumPyEigensolver``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - k: int = 1, - filter_criterion: Callable[ - [list | np.ndarray, float, ListOrDict[float] | None], bool - ] = None, - ) -> None: - """ - Args: - k: How many eigenvalues are to be computed, has a min. value of 1. - filter_criterion: callable that allows to filter eigenvalues/eigenstates, only feasible - eigenstates are returned in the results. The callable has the signature - `filter(eigenstate, eigenvalue, aux_values)` and must return a boolean to indicate - whether to keep this value in the final returned result or not. If the number of - elements that satisfies the criterion is smaller than `k` then the returned list has - fewer elements and can even be empty. - """ - validate_min("k", k, 1) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - super().__init__() - - self._in_k = k - self._k = k - - self._filter_criterion = filter_criterion - - self._ret = EigensolverResult() - - @property - def k(self) -> int: - """returns k (number of eigenvalues requested)""" - return self._in_k - - @k.setter - def k(self, k: int) -> None: - """set k (number of eigenvalues requested)""" - validate_min("k", k, 1) - self._in_k = k - self._k = k - - @property - def filter_criterion( - self, - ) -> Callable[[list | np.ndarray, float, ListOrDict[float] | None], bool] | None: - """returns the filter criterion if set""" - return self._filter_criterion - - @filter_criterion.setter - def filter_criterion( - self, - filter_criterion: Callable[[list | np.ndarray, float, ListOrDict[float] | None], bool] - | None, - ) -> None: - """set the filter criterion""" - self._filter_criterion = filter_criterion - - @classmethod - def supports_aux_operators(cls) -> bool: - return True - - def _check_set_k(self, operator: OperatorBase) -> None: - if operator is not None: - if self._in_k > 2**operator.num_qubits: - self._k = 2**operator.num_qubits - logger.debug( - "WARNING: Asked for %s eigenvalues but max possible is %s.", self._in_k, self._k - ) - else: - self._k = self._in_k - - def _solve(self, operator: OperatorBase) -> None: - sp_mat = operator.to_spmatrix() - # If matrix is diagonal, the elements on the diagonal are the eigenvalues. Solve by sorting. - if scisparse.csr_matrix(sp_mat.diagonal()).nnz == sp_mat.nnz: - diag = sp_mat.diagonal() - indices = np.argsort(diag)[: self._k] - eigval = diag[indices] - eigvec = np.zeros((sp_mat.shape[0], self._k)) - for i, idx in enumerate(indices): - eigvec[idx, i] = 1.0 - else: - if self._k >= 2**operator.num_qubits - 1: - logger.debug("SciPy doesn't support to get all eigenvalues, using NumPy instead.") - if operator.is_hermitian(): - eigval, eigvec = np.linalg.eigh(operator.to_matrix()) - else: - eigval, eigvec = np.linalg.eig(operator.to_matrix()) - else: - if operator.is_hermitian(): - eigval, eigvec = scisparse.linalg.eigsh(sp_mat, k=self._k, which="SA") - else: - eigval, eigvec = scisparse.linalg.eigs(sp_mat, k=self._k, which="SR") - indices = np.argsort(eigval)[: self._k] - eigval = eigval[indices] - eigvec = eigvec[:, indices] - self._ret.eigenvalues = eigval - self._ret.eigenstates = eigvec.T - - def _get_ground_state_energy(self, operator: OperatorBase) -> None: - if self._ret.eigenvalues is None or self._ret.eigenstates is None: - self._solve(operator) - - def _get_energies( - self, operator: OperatorBase, aux_operators: ListOrDict[OperatorBase] | None - ) -> None: - if self._ret.eigenvalues is None or self._ret.eigenstates is None: - self._solve(operator) - - if aux_operators is not None: - aux_op_vals = [] - for i in range(self._k): - aux_op_vals.append( - self._eval_aux_operators(aux_operators, self._ret.eigenstates[i]) - ) - self._ret.aux_operator_eigenvalues = aux_op_vals - - @staticmethod - def _eval_aux_operators( - aux_operators: ListOrDict[OperatorBase], wavefn, threshold: float = 1e-12 - ) -> ListOrDict[tuple[complex, complex]]: - - values: ListOrDict[tuple[complex, complex]] - - # As a list, aux_operators can contain None operators for which None values are returned. - # As a dict, the None operators in aux_operators have been dropped in compute_eigenvalues. - if isinstance(aux_operators, list): - values = [None] * len(aux_operators) - key_op_iterator = enumerate(aux_operators) - else: - values = {} - key_op_iterator = aux_operators.items() - for key, operator in key_op_iterator: - if operator is None: - continue - value = 0.0 - if operator.coeff != 0: - mat = operator.to_spmatrix() - # Terra doesn't support sparse yet, so do the matmul directly if so - # This is necessary for the particle_hole and other chemistry tests because the - # pauli conversions are 2^12th large and will OOM error if not sparse. - if isinstance(mat, scisparse.spmatrix): - value = mat.dot(wavefn).dot(np.conj(wavefn)) - else: - value = StateFn(operator, is_measurement=True).eval(wavefn) - value = value if np.abs(value) > threshold else 0.0 - # The value get's wrapped into a tuple: (mean, standard deviation). - # Since this is an exact computation, the standard deviation is known to be zero. - values[key] = (value, 0.0) - return values - - def compute_eigenvalues( - self, operator: OperatorBase, aux_operators: ListOrDict[OperatorBase] | None = None - ) -> EigensolverResult: - super().compute_eigenvalues(operator, aux_operators) - - if operator is None: - raise AlgorithmError("Operator was never provided") - - self._check_set_k(operator) - zero_op = I.tensorpower(operator.num_qubits) * 0.0 - if isinstance(aux_operators, list) and len(aux_operators) > 0: - # For some reason Chemistry passes aux_ops with 0 qubits and paulis sometimes. - aux_operators = [zero_op if op == 0 else op for op in aux_operators] - elif isinstance(aux_operators, dict) and len(aux_operators) > 0: - aux_operators = { - key: zero_op if op == 0 else op # Convert zero values to zero operators - for key, op in aux_operators.items() - if op is not None # Discard None values - } - else: - aux_operators = None - - k_orig = self._k - if self._filter_criterion: - # need to consider all elements if a filter is set - self._k = 2**operator.num_qubits - - self._ret = EigensolverResult() - self._solve(operator) - - # compute energies before filtering, as this also evaluates the aux operators - self._get_energies(operator, aux_operators) - - # if a filter is set, loop over the given values and only keep - if self._filter_criterion: - - eigvecs = [] - eigvals = [] - aux_ops = [] - cnt = 0 - for i in range(len(self._ret.eigenvalues)): - eigvec = self._ret.eigenstates[i] - eigval = self._ret.eigenvalues[i] - if self._ret.aux_operator_eigenvalues is not None: - aux_op = self._ret.aux_operator_eigenvalues[i] - else: - aux_op = None - if self._filter_criterion(eigvec, eigval, aux_op): - cnt += 1 - eigvecs += [eigvec] - eigvals += [eigval] - if self._ret.aux_operator_eigenvalues is not None: - aux_ops += [aux_op] - if cnt == k_orig: - break - - self._ret.eigenstates = np.array(eigvecs) - self._ret.eigenvalues = np.array(eigvals) - # conversion to np.array breaks in case of aux_ops - self._ret.aux_operator_eigenvalues = aux_ops - - self._k = k_orig - - # evaluate ground state after filtering (in case a filter is set) - self._get_ground_state_energy(operator) - if self._ret.eigenstates is not None: - self._ret.eigenstates = ListOp([StateFn(vec) for vec in self._ret.eigenstates]) - - logger.debug("EigensolverResult:\n%s", self._ret) - return self._ret diff --git a/qiskit/algorithms/eigen_solvers/vqd.py b/qiskit/algorithms/eigen_solvers/vqd.py deleted file mode 100644 index 068cdd08cd14..000000000000 --- a/qiskit/algorithms/eigen_solvers/vqd.py +++ /dev/null @@ -1,807 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Variational Quantum Deflation Algorithm for computing higher energy states. - -See https://arxiv.org/abs/1805.08138. -""" -from __future__ import annotations - -import logging -import warnings -from collections.abc import Callable -from time import time -import numpy as np - -from qiskit.circuit import QuantumCircuit, Parameter -from qiskit.circuit.library import RealAmplitudes -from qiskit.opflow.primitive_ops.pauli_op import PauliOp -from qiskit.providers import Backend -from qiskit.opflow import ( - OperatorBase, - ExpectationBase, - ExpectationFactory, - StateFn, - CircuitStateFn, - ListOp, - CircuitSampler, - PauliSumOp, -) -from qiskit.opflow.gradients import GradientBase -from qiskit.utils.validation import validate_min -from qiskit.utils.backend_utils import is_aer_provider -from qiskit.utils.deprecation import deprecate_func -from qiskit.utils import QuantumInstance -from ..list_or_dict import ListOrDict -from ..optimizers import Optimizer, SLSQP, Minimizer -from ..variational_algorithm import VariationalAlgorithm, VariationalResult -from .eigen_solver import Eigensolver, EigensolverResult -from ..minimum_eigen_solvers.vqe import _validate_bounds, _validate_initial_point -from ..exceptions import AlgorithmError -from ..aux_ops_evaluator import eval_observables - -logger = logging.getLogger(__name__) - - -class VQD(VariationalAlgorithm, Eigensolver): - r"""Deprecated: Variational Quantum Deflation algorithm. - - The VQD class has been superseded by the - :class:`qiskit.algorithms.eigensolvers.VQD` class. - This class will be deprecated in a future release and subsequently - removed after that. - - `VQD `__ is a quantum algorithm that uses a - variational technique to find - the k eigenvalues of the Hamiltonian :math:`H` of a given system. - - The algorithm computes excited state energies of generalised hamiltonians - by optimising over a modified cost function where each succesive eigen value - is calculated iteratively by introducing an overlap term with all - the previously computed eigenstaes that must be minimised, thus ensuring - higher energy eigen states are found. - - An instance of VQD requires defining three algorithmic sub-components: - an integer k denoting the number of eigenstates to calculate, a trial - state (a.k.a. ansatz)which is a :class:`QuantumCircuit`, - and one of the classical :mod:`~qiskit.algorithms.optimizers`. - The ansatz is varied, via its set of parameters, by the optimizer, - such that it works towards a state, as determined by the parameters - applied to the ansatz, that will result in the minimum expectation values - being measured of the input operator (Hamiltonian). The algorithm does - this by iteratively refining each excited state to be orthogonal to all - the previous excited states. - - An optional array of parameter values, via the *initial_point*, may be provided as the - starting point for the search of the minimum eigenvalue. This feature is particularly useful - such as when there are reasons to believe that the solution point is close to a particular - point. - - The length of the *initial_point* list value must match the number of the parameters - expected by the ansatz being used. If the *initial_point* is left at the default - of ``None``, then VQD will look to the ansatz for a preferred value, based on its - given initial state. If the ansatz returns ``None``, - then a random point will be generated within the parameter bounds set, as per above. - If the ansatz provides ``None`` as the lower bound, then VQD - will default it to :math:`-2\pi`; similarly, if the ansatz returns ``None`` - as the upper bound, the default value will be :math:`2\pi`. - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.eigensolvers.VQD``." - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - ansatz: QuantumCircuit | None = None, - k: int = 2, - betas: list[float] | None = None, - optimizer: Optimizer | Minimizer | None = None, - initial_point: np.ndarray | None = None, - gradient: GradientBase | Callable | None = None, - expectation: ExpectationBase | None = None, - include_custom: bool = False, - max_evals_grouped: int = 1, - callback: Callable[[int, np.ndarray, float, float, int], None] | None = None, - quantum_instance: QuantumInstance | Backend | None = None, - ) -> None: - """ - - Args: - ansatz: A parameterized circuit used as ansatz for the wave function. - k: the number of eigenvalues to return. Returns the lowest k eigenvalues. - betas: beta parameters in the VQD paper. - Should have length k - 1, with k the number of excited states. - These hyperparameters balance the contribution of each overlap term to the cost - function and have a default value computed as the mean square sum of the - coefficients of the observable. - optimizer: A classical optimizer. Can either be a Qiskit optimizer or a callable - that takes an array as input and returns a Qiskit or SciPy optimization result. - initial_point: An optional initial point (i.e. initial parameter values) - for the optimizer. If ``None`` then VQD will look to the ansatz for a preferred - point and if not will simply compute a random one. - gradient: An optional gradient function or operator for optimizer. - Only used to compute the ground state at the moment. - expectation: The Expectation converter for taking the average value of the - Observable over the ansatz state function. When ``None`` (the default) an - :class:`~qiskit.opflow.expectations.ExpectationFactory` is used to select - an appropriate expectation based on the operator and backend. When using Aer - qasm_simulator backend, with paulis, it is however much faster to leverage custom - Aer function for the computation but, although VQD performs much faster - with it, the outcome is ideal, with no shot noise, like using a state vector - simulator. If you are just looking for the quickest performance when choosing Aer - qasm_simulator and the lack of shot noise is not an issue then set `include_custom` - parameter here to ``True`` (defaults to ``False``). - include_custom: When `expectation` parameter here is None setting this to ``True`` will - allow the factory to include the custom Aer pauli expectation. - max_evals_grouped: Max number of evaluations performed simultaneously. Signals the - given optimizer that more than one set of parameters can be supplied so that - multiple points to compute the gradient can be passed and if computed in parallel - potentially the expectation values can be computed in parallel. Typically this is - possible when a finite difference gradient is used by the optimizer such that - improve overall execution time. Deprecated if a gradient operator or function is - given. - callback: a callback that can access the intermediate data during the optimization. - Four parameter values are passed to the callback as follows during each evaluation - by the optimizer for its current set of parameters as it works towards the minimum. - These are: the evaluation count, the optimizer parameters for the ansatz, the - evaluated mean, the evaluated standard deviation, and the current step. - quantum_instance: Quantum Instance or Backend - - """ - validate_min("max_evals_grouped", max_evals_grouped, 1) - - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - super().__init__() - - self._max_evals_grouped = max_evals_grouped - self._circuit_sampler: CircuitSampler | None = None - self._expectation = None - self.expectation = expectation - self._include_custom = include_custom - - # set ansatz -- still supporting pre 0.18.0 sorting - - self._ansatz: QuantumCircuit | None = None - self.ansatz = ansatz - - self.k = k - self.betas = betas - - self._optimizer: Optimizer | None = None - self.optimizer = optimizer - - self._initial_point: np.ndarray | None = None - self.initial_point = initial_point - self._gradient: GradientBase | Callable | None = None - self.gradient = gradient - self._quantum_instance: QuantumInstance | None = None - - if quantum_instance is not None: - self.quantum_instance = quantum_instance - - self._eval_time = None - self._eval_count = 0 - self._callback: Callable[[int, np.ndarray, float, float, int], None] | None = None - self.callback = callback - - logger.info(self.print_settings()) - - @property - def ansatz(self) -> QuantumCircuit: - """Returns the ansatz.""" - return self._ansatz - - @ansatz.setter - def ansatz(self, ansatz: QuantumCircuit | None): - """Sets the ansatz. - - Args: - ansatz: The parameterized circuit used as an ansatz. - If None is passed, RealAmplitudes is used by default. - - """ - if ansatz is None: - ansatz = RealAmplitudes() - - self._ansatz = ansatz - - @property - def gradient(self) -> GradientBase | Callable | None: - """Returns the gradient.""" - return self._gradient - - @gradient.setter - def gradient(self, gradient: GradientBase | Callable | None): - """Sets the gradient.""" - self._gradient = gradient - - @property - def quantum_instance(self) -> QuantumInstance | None: - """Returns quantum instance.""" - return self._quantum_instance - - @quantum_instance.setter - def quantum_instance(self, quantum_instance: QuantumInstance | Backend) -> None: - """Sets a quantum_instance.""" - if not isinstance(quantum_instance, QuantumInstance): - quantum_instance = QuantumInstance(quantum_instance) - - self._quantum_instance = quantum_instance - self._circuit_sampler = CircuitSampler( - quantum_instance, param_qobj=is_aer_provider(quantum_instance.backend) - ) - - @property - def initial_point(self) -> np.ndarray | None: - """Returns initial point.""" - return self._initial_point - - @initial_point.setter - def initial_point(self, initial_point: np.ndarray): - """Sets initial point""" - self._initial_point = initial_point - - @property - def max_evals_grouped(self) -> int: - """Returns max_evals_grouped""" - return self._max_evals_grouped - - @max_evals_grouped.setter - def max_evals_grouped(self, max_evals_grouped: int): - """Sets max_evals_grouped""" - self._max_evals_grouped = max_evals_grouped - self.optimizer.set_max_evals_grouped(max_evals_grouped) - - @property - def include_custom(self) -> bool: - """Returns include_custom""" - return self._include_custom - - @include_custom.setter - def include_custom(self, include_custom: bool): - """Sets include_custom. If set to another value than the one that was previsously set, - the expectation attribute is reset to None. - """ - if include_custom != self._include_custom: - self._include_custom = include_custom - self.expectation = None - - @property - def callback(self) -> Callable[[int, np.ndarray, float, float, int], None] | None: - """Returns callback""" - return self._callback - - @callback.setter - def callback(self, callback: Callable[[int, np.ndarray, float, float, int], None] | None): - """Sets callback""" - self._callback = callback - - @property - def expectation(self) -> ExpectationBase | None: - """The expectation value algorithm used to construct the expectation measurement from - the observable.""" - return self._expectation - - @expectation.setter - def expectation(self, exp: ExpectationBase | None) -> None: - self._expectation = exp - - def _check_operator_ansatz(self, operator: OperatorBase): - """Check that the number of qubits of operator and ansatz match.""" - if operator is not None and self.ansatz is not None: - if operator.num_qubits != self.ansatz.num_qubits: - # try to set the number of qubits on the ansatz, if possible - try: - self.ansatz.num_qubits = operator.num_qubits - except AttributeError as ex: - raise AlgorithmError( - "The number of qubits of the ansatz does not match the " - "operator, and the ansatz does not allow setting the " - "number of qubits using `num_qubits`." - ) from ex - - @property - def optimizer(self) -> Optimizer: - """Returns optimizer""" - return self._optimizer - - @optimizer.setter - def optimizer(self, optimizer: Optimizer | None): - """Sets the optimizer attribute. - - Args: - optimizer: The optimizer to be used. If None is passed, SLSQP is used by default. - - """ - if optimizer is None: - optimizer = SLSQP() - - if isinstance(optimizer, Optimizer): - optimizer.set_max_evals_grouped(self.max_evals_grouped) - - self._optimizer = optimizer - - @property - def setting(self): - """Prepare the setting of VQD as a string.""" - ret = f"Algorithm: {self.__class__.__name__}\n" - params = "" - for key, value in self.__dict__.items(): - if key[0] == "_": - if "initial_point" in key and value is None: - params += "-- {}: {}\n".format(key[1:], "Random seed") - else: - params += f"-- {key[1:]}: {value}\n" - ret += f"{params}" - return ret - - def print_settings(self): - """Preparing the setting of VQD into a string. - - Returns: - str: the formatted setting of VQD. - """ - ret = "\n" - ret += "==================== Setting of {} ============================\n".format( - self.__class__.__name__ - ) - ret += f"{self.setting}" - ret += "===============================================================\n" - if self.ansatz is not None: - ret += "{}".format(self.ansatz.draw(output="text")) - else: - ret += "ansatz has not been set" - ret += "===============================================================\n" - ret += f"{self._optimizer.setting}" - ret += "===============================================================\n" - return ret - - def construct_expectation( - self, - parameter: list[float] | list[Parameter] | np.ndarray, - operator: OperatorBase, - return_expectation: bool = False, - ) -> OperatorBase | tuple[OperatorBase, ExpectationBase]: - r""" - Generate the ansatz circuit and expectation value measurement, and return their - runnable composition. - - Args: - parameter: Parameters for the ansatz circuit. - operator: Qubit operator of the Observable - return_expectation: If True, return the ``ExpectationBase`` expectation converter used - in the construction of the expectation value. Useful e.g. to compute the standard - deviation of the expectation value. - - Returns: - The Operator equalling the measurement of the ansatz :class:`StateFn` by the - Observable's expectation :class:`StateFn`, and, optionally, the expectation converter. - - Raises: - AlgorithmError: If no operator has been provided. - AlgorithmError: If no expectation is passed and None could be inferred via the - ExpectationFactory. - """ - if operator is None: - raise AlgorithmError("The operator was never provided.") - - self._check_operator_ansatz(operator) - - # if expectation was never created, try to create one - if self.expectation is None: - expectation = ExpectationFactory.build( - operator=operator, - backend=self.quantum_instance, - include_custom=self._include_custom, - ) - else: - expectation = self.expectation - - wave_function = self.ansatz.assign_parameters(parameter) - - observable_meas = expectation.convert(StateFn(operator, is_measurement=True)) - ansatz_circuit_op = CircuitStateFn(wave_function) - expect_op = observable_meas.compose(ansatz_circuit_op).reduce() - - if return_expectation: - return expect_op, expectation - - return expect_op - - def construct_circuit( - self, - parameter: list[float] | list[Parameter] | np.ndarray, - operator: OperatorBase, - ) -> list[QuantumCircuit]: - """Return the circuits used to compute the expectation value. - - Args: - parameter: Parameters for the ansatz circuit. - operator: Qubit operator of the Observable - - Returns: - A list of the circuits used to compute the expectation value. - """ - expect_op = self.construct_expectation(parameter, operator).to_circuit_op() - - circuits = [] - - # recursively extract circuits - def extract_circuits(op): - if isinstance(op, CircuitStateFn): - circuits.append(op.primitive) - elif isinstance(op, ListOp): - for op_i in op.oplist: - extract_circuits(op_i) - - extract_circuits(expect_op) - - return circuits - - @classmethod - def supports_aux_operators(cls) -> bool: - return True - - def _eval_aux_ops( - self, - parameters: np.ndarray, - aux_operators: ListOrDict[OperatorBase], - expectation: ExpectationBase, - threshold: float = 1e-12, - ) -> ListOrDict[tuple[complex, complex]]: - # Create new CircuitSampler to avoid breaking existing one's caches. - sampler = CircuitSampler(self.quantum_instance) - - if isinstance(aux_operators, dict): - list_op = ListOp(list(aux_operators.values())) - else: - list_op = ListOp(aux_operators) - - aux_op_meas = expectation.convert(StateFn(list_op, is_measurement=True)) - aux_op_expect = aux_op_meas.compose( - CircuitStateFn(self.ansatz.assign_parameters(parameters)) - ) - aux_op_expect_sampled = sampler.convert(aux_op_expect) - - # compute means - values = np.real(aux_op_expect_sampled.eval()) - - # compute standard deviations - variances = np.real(expectation.compute_variance(aux_op_expect_sampled)) - if not isinstance(variances, np.ndarray) and variances == 0.0: - # when `variances` is a single value equal to 0., our expectation value is exact and we - # manually ensure the variances to be a list of the correct length - variances = np.zeros(len(aux_operators), dtype=float) - std_devs = np.sqrt(variances / self.quantum_instance.run_config.shots) - - # Discard values below threshold - aux_op_means = values * (np.abs(values) > threshold) - # zip means and standard deviations into tuples - aux_op_results = zip(aux_op_means, std_devs) - - # Return None eigenvalues for None operators if aux_operators is a list. - # None operators are already dropped in compute_minimum_eigenvalue if aux_operators is a - # dict. - if isinstance(aux_operators, list): - aux_operator_eigenvalues: ListOrDict[tuple[complex, complex]] = [None] * len( - aux_operators - ) - key_value_iterator = enumerate(aux_op_results) - else: - aux_operator_eigenvalues = {} - key_value_iterator = zip(aux_operators.keys(), aux_op_results) - - for key, value in key_value_iterator: - if aux_operators[key] is not None: - aux_operator_eigenvalues[key] = value - - return aux_operator_eigenvalues - - def compute_eigenvalues( - self, operator: OperatorBase, aux_operators: ListOrDict[OperatorBase] | None = None - ) -> EigensolverResult: - super().compute_eigenvalues(operator, aux_operators) - - if self.quantum_instance is None: - raise AlgorithmError( - "A QuantumInstance or Backend must be supplied to run the quantum algorithm." - ) - self.quantum_instance.circuit_summary = True - - # this sets the size of the ansatz, so it must be called before the initial point - # validation - self._check_operator_ansatz(operator) - - # set an expectation for this algorithm run (will be reset to None at the end) - initial_point = _validate_initial_point(self.initial_point, self.ansatz) - - bounds = _validate_bounds(self.ansatz) - # We need to handle the array entries being zero or Optional i.e. having value None - if aux_operators: - zero_op = PauliSumOp.from_list([("I" * self.ansatz.num_qubits, 0)]) - - # Convert the None and zero values when aux_operators is a list. - # Drop None and convert zero values when aux_operators is a dict. - if isinstance(aux_operators, list): - key_op_iterator = enumerate(aux_operators) - converted: ListOrDict[OperatorBase] = [zero_op] * len(aux_operators) - else: - key_op_iterator = aux_operators.items() - converted = {} - for key, op in key_op_iterator: - if op is not None: - converted[key] = zero_op if op == 0 else op - - aux_operators = converted - - else: - aux_operators = None - - if self.betas is None: - upper_bound = ( - abs(operator.coeff) - if isinstance(operator, PauliOp) - else abs(operator.coeff) * sum(abs(operation.coeff) for operation in operator) - ) - self.betas = [upper_bound * 10] * (self.k) - logger.info("beta autoevaluated to %s", self.betas[0]) - - result = VQDResult() - result.optimal_point = [] - result.optimal_parameters = [] - result.optimal_value = [] - result.cost_function_evals = [] - result.optimizer_time = [] - result.eigenvalues = [] - result.eigenstates = [] - - if aux_operators is not None: - aux_values = [] - - for step in range(1, self.k + 1): - - self._eval_count = 0 - energy_evaluation, expectation = self.get_energy_evaluation( - step, operator, return_expectation=True, prev_states=result.optimal_parameters - ) - - # Convert the gradient operator into a callable function that is compatible with the - # optimization routine. Only used for the ground state currently as Gradient() doesnt - # support SumOps yet - if isinstance(self._gradient, GradientBase): - gradient = self._gradient.gradient_wrapper( - StateFn(operator, is_measurement=True) @ StateFn(self.ansatz), - bind_params=list(self.ansatz.parameters), - backend=self._quantum_instance, - ) - else: - gradient = self._gradient - - start_time = time() - - if callable(self.optimizer): - opt_result = self.optimizer( # pylint: disable=not-callable - fun=energy_evaluation, x0=initial_point, jac=gradient, bounds=bounds - ) - else: - opt_result = self.optimizer.minimize( - fun=energy_evaluation, x0=initial_point, jac=gradient, bounds=bounds - ) - - eval_time = time() - start_time - - result.optimal_point.append(opt_result.x) - result.optimal_parameters.append(dict(zip(self.ansatz.parameters, opt_result.x))) - result.optimal_value.append(opt_result.fun) - result.cost_function_evals.append(opt_result.nfev) - result.optimizer_time.append(eval_time) - - eigenvalue = ( - StateFn(operator, is_measurement=True) - .compose( - CircuitStateFn(self.ansatz.assign_parameters(result.optimal_parameters[-1])) - ) - .reduce() - .eval() - ) - - result.eigenvalues.append(eigenvalue) - result.eigenstates.append(self._get_eigenstate(result.optimal_parameters[-1])) - - if aux_operators is not None: - bound_ansatz = self.ansatz.assign_parameters(result.optimal_point[-1]) - aux_value = eval_observables( - self.quantum_instance, bound_ansatz, aux_operators, expectation=expectation - ) - aux_values.append(aux_value) - - if step == 1: - - logger.info( - "Ground state optimization complete in %s seconds.\n" - "Found opt_params %s in %s evals", - eval_time, - result.optimal_point, - self._eval_count, - ) - else: - logger.info( - ( - "%s excited state optimization complete in %s s.\n" - "Found opt_params %s in %s evals" - ), - str(step - 1), - eval_time, - result.optimal_point, - self._eval_count, - ) - - # To match the signature of NumpyEigenSolver Result - result.eigenstates = ListOp([StateFn(vec) for vec in result.eigenstates]) - result.eigenvalues = np.array(result.eigenvalues) - result.optimal_point = np.array(result.optimal_point) - result.optimal_value = np.array(result.optimal_value) - result.cost_function_evals = np.array(result.cost_function_evals) - result.optimizer_time = np.array(result.optimizer_time) - - if aux_operators is not None: - result.aux_operator_eigenvalues = aux_values - - return result - - def get_energy_evaluation( - self, - step: int, - operator: OperatorBase, - return_expectation: bool = False, - prev_states: list[np.ndarray] | None = None, - ) -> Callable[[np.ndarray], float | list[float]] | tuple[ - Callable[[np.ndarray], float | list[float]], ExpectationBase - ]: - """Returns a function handle to evaluates the energy at given parameters for the ansatz. - - This return value is the objective function to be passed to the optimizer for evaluation. - - Args: - step: level of energy being calculated. 0 for ground, 1 for first excited state... - operator: The operator whose energy to evaluate. - return_expectation: If True, return the ``ExpectationBase`` expectation converter used - in the construction of the expectation value. Useful e.g. to evaluate other - operators with the same expectation value converter. - prev_states: List of parameters from previous rounds of optimization. - - - Returns: - A callable that computes and returns the energy of the hamiltonian - of each parameter, and, optionally, the expectation - - Raises: - RuntimeError: If the circuit is not parameterized (i.e. has 0 free parameters). - AlgorithmError: If operator was not provided. - - """ - - num_parameters = self.ansatz.num_parameters - if num_parameters == 0: - raise RuntimeError("The ansatz must be parameterized, but has 0 free parameters.") - - if operator is None: - raise AlgorithmError("The operator was never provided.") - - if step > 1 and (len(prev_states) + 1) != step: - raise RuntimeError( - f"Passed previous states of the wrong size." - f"Passed array has length {str(len(prev_states))}" - ) - - self._check_operator_ansatz(operator) - overlap_op = [] - - ansatz_params = self.ansatz.parameters - expect_op, expectation = self.construct_expectation( - ansatz_params, operator, return_expectation=True - ) - - for state in range(step - 1): - - prev_circ = self.ansatz.assign_parameters(prev_states[state]) - overlap_op.append(~CircuitStateFn(prev_circ) @ CircuitStateFn(self.ansatz)) - - def energy_evaluation(parameters): - parameter_sets = np.reshape(parameters, (-1, num_parameters)) - # Dict associating each parameter with the lists of parameterization values for it - param_bindings = dict(zip(ansatz_params, parameter_sets.transpose().tolist())) - - sampled_expect_op = self._circuit_sampler.convert(expect_op, params=param_bindings) - means = np.real(sampled_expect_op.eval()) - - for state in range(step - 1): - sampled_final_op = self._circuit_sampler.convert( - overlap_op[state], params=param_bindings - ) - cost = sampled_final_op.eval() - means += np.real(self.betas[state] * np.conj(cost) * cost) - - if self._callback is not None: - variance = np.real(expectation.compute_variance(sampled_expect_op)) - estimator_error = np.sqrt(variance / self.quantum_instance.run_config.shots) - for i, param_set in enumerate(parameter_sets): - self._eval_count += 1 - self._callback(self._eval_count, param_set, means[i], estimator_error[i], step) - else: - self._eval_count += len(means) - - return means if len(means) > 1 else means[0] - - if return_expectation: - return energy_evaluation, expectation - - return energy_evaluation - - def _get_eigenstate(self, optimal_parameters) -> list[float] | dict[str, int]: - """Get the simulation outcome of the ansatz, provided with parameters.""" - optimal_circuit = self.ansatz.assign_parameters(optimal_parameters) - state_fn = self._circuit_sampler.convert(StateFn(optimal_circuit)).eval() - if self.quantum_instance.is_statevector: - state = state_fn.primitive.data # VectorStateFn -> Statevector -> np.array - else: - state = state_fn.to_dict_fn().primitive # SparseVectorStateFn -> DictStateFn -> dict - - return state - - -class VQDResult(VariationalResult, EigensolverResult): - """Deprecated: VQD Result. - - The VQDResult class has been superseded by the - :class:`qiskit.algorithms.eigensolvers.VQDResult` class. - This class will be deprecated in a future release and subsequently - removed after that. - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.eigensolvers.VQDResult``." - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__(self) -> None: - super().__init__() - self._cost_function_evals: int | None = None - - @property - def cost_function_evals(self) -> int | None: - """Returns number of cost optimizer evaluations""" - return self._cost_function_evals - - @cost_function_evals.setter - def cost_function_evals(self, value: int) -> None: - """Sets number of cost function evaluations""" - self._cost_function_evals = value - - @property - def eigenstates(self) -> np.ndarray | None: - """return eigen state""" - return self._eigenstates - - @eigenstates.setter - def eigenstates(self, value: np.ndarray) -> None: - """set eigen state""" - self._eigenstates = value diff --git a/qiskit/algorithms/eigensolvers/__init__.py b/qiskit/algorithms/eigensolvers/__init__.py deleted file mode 100644 index 2934d6c2a340..000000000000 --- a/qiskit/algorithms/eigensolvers/__init__.py +++ /dev/null @@ -1,53 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -===================================================================== -Eigensolvers Package (:mod:`qiskit.algorithms.eigensolvers`) -===================================================================== - -.. currentmodule:: qiskit.algorithms.eigensolvers - -Eigensolvers -================ - -.. autosummary:: - :toctree: ../stubs/ - - Eigensolver - NumPyEigensolver - VQD - -Results -======= - - .. autosummary:: - :toctree: ../stubs/ - - EigensolverResult - NumPyEigensolverResult - VQDResult - -""" - -from .numpy_eigensolver import NumPyEigensolver, NumPyEigensolverResult -from .eigensolver import Eigensolver, EigensolverResult -from .vqd import VQD, VQDResult - -__all__ = [ - "NumPyEigensolver", - "NumPyEigensolverResult", - "Eigensolver", - "EigensolverResult", - "VQD", - "VQDResult", -] diff --git a/qiskit/algorithms/eigensolvers/eigensolver.py b/qiskit/algorithms/eigensolvers/eigensolver.py deleted file mode 100644 index 30fb7c488844..000000000000 --- a/qiskit/algorithms/eigensolvers/eigensolver.py +++ /dev/null @@ -1,106 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The eigensolver interface and result.""" - -from __future__ import annotations - -from abc import ABC, abstractmethod -from typing import Any -import numpy as np - -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..algorithm_result import AlgorithmResult -from ..list_or_dict import ListOrDict - - -class Eigensolver(ABC): - """The eigensolver interface. - - Algorithms that can compute eigenvalues for an operator - may implement this interface to allow different algorithms to be - used interchangeably. - """ - - @abstractmethod - def compute_eigenvalues( - self, - operator: BaseOperator | PauliSumOp, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - ) -> "EigensolverResult": - """ - Computes the minimum eigenvalue. The ``operator`` and ``aux_operators`` are supplied here. - While an ``operator`` is required by algorithms, ``aux_operators`` are optional. - - Args: - operator: Qubit operator of the observable. - aux_operators: Optional list of auxiliary operators to be evaluated with the - eigenstate of the minimum eigenvalue main result and their expectation values - returned. For instance, in chemistry, these can be dipole operators and total particle - count operators, so we can get values for these at the ground state. - - Returns: - An eigensolver result. - """ - return EigensolverResult() - - @classmethod - def supports_aux_operators(cls) -> bool: - """Whether computing the expectation value of auxiliary operators is supported. - - If the eigensolver computes the eigenvalues of the main operator, then it can compute - the expectation value of the ``aux_operators`` for that state. Otherwise they will be ignored. - - Returns: - ``True`` if ``aux_operator`` expectations can be evaluated, ``False`` otherwise. - """ - return False - - -class EigensolverResult(AlgorithmResult): - """Eigensolver result.""" - - def __init__(self) -> None: - super().__init__() - self._eigenvalues: np.ndarray | None = None - self._aux_operators_evaluated: list[ - ListOrDict[tuple[complex, dict[str, Any]]] - ] | None = None - - @property - def eigenvalues(self) -> np.ndarray | None: - """Return the eigenvalues.""" - return self._eigenvalues - - @eigenvalues.setter - def eigenvalues(self, value: np.ndarray) -> None: - """Set the eigenvalues.""" - self._eigenvalues = value - - @property - def aux_operators_evaluated( - self, - ) -> list[ListOrDict[tuple[complex, dict[str, Any]]]] | None: - """Return the aux operator expectation values. - - These values are in fact tuples formatted as (mean, metadata). - """ - return self._aux_operators_evaluated - - @aux_operators_evaluated.setter - def aux_operators_evaluated( - self, value: list[ListOrDict[tuple[complex, dict[str, Any]]]] - ) -> None: - """Set the aux operator eigenvalues.""" - self._aux_operators_evaluated = value diff --git a/qiskit/algorithms/eigensolvers/numpy_eigensolver.py b/qiskit/algorithms/eigensolvers/numpy_eigensolver.py deleted file mode 100644 index c0af8382ccd4..000000000000 --- a/qiskit/algorithms/eigensolvers/numpy_eigensolver.py +++ /dev/null @@ -1,327 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The NumPy eigensolver algorithm.""" - -from __future__ import annotations - -import warnings -from typing import Callable, Union, List, Optional -import logging -import numpy as np -from scipy import sparse as scisparse - -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info import SparsePauliOp, Statevector -from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.utils.validation import validate_min - -from .eigensolver import Eigensolver, EigensolverResult -from ..exceptions import AlgorithmError -from ..list_or_dict import ListOrDict - -logger = logging.getLogger(__name__) - -FilterType = Callable[[Union[List, np.ndarray], float, Optional[ListOrDict[float]]], bool] - - -class NumPyEigensolver(Eigensolver): - r""" - The NumPy eigensolver algorithm. - - The NumPy Eigensolver computes up to the first :math:`k` eigenvalues of a complex-valued square - matrix of dimension :math:`n \times n`, with :math:`k \leq n`. - - Note: - Operators are automatically converted to SciPy's ``spmatrix`` - as needed and this conversion can be costly in terms of memory and performance as the - operator size, mostly in terms of number of qubits it represents, gets larger. - """ - - def __init__( - self, - k: int = 1, - filter_criterion: FilterType | None = None, - ) -> None: - """ - Args: - k: Number of eigenvalues are to be computed, with a minimum value of 1. - filter_criterion: Callable that allows to filter eigenvalues/eigenstates. Only feasible - eigenstates are returned in the results. The callable has the signature - ``filter(eigenstate, eigenvalue, aux_values)`` and must return a boolean to indicate - whether to keep this value in the final returned result or not. If the number of - elements that satisfies the criterion is smaller than ``k``, then the returned list will - have fewer elements and can even be empty. - """ - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - validate_min("k", k, 1) - - super().__init__() - - self._in_k = k - self._k = k - - self._filter_criterion = filter_criterion - - @property - def k(self) -> int: - """Return k (number of eigenvalues requested).""" - return self._in_k - - @k.setter - def k(self, k: int) -> None: - """Set k (number of eigenvalues requested).""" - validate_min("k", k, 1) - self._in_k = k - self._k = k - - @property - def filter_criterion( - self, - ) -> FilterType | None: - """Return the filter criterion if set.""" - return self._filter_criterion - - @filter_criterion.setter - def filter_criterion(self, filter_criterion: FilterType | None) -> None: - """Set the filter criterion.""" - self._filter_criterion = filter_criterion - - @classmethod - def supports_aux_operators(cls) -> bool: - return True - - def _check_set_k(self, operator: BaseOperator | PauliSumOp) -> None: - if operator is not None: - if self._in_k > 2**operator.num_qubits: - self._k = 2**operator.num_qubits - logger.debug( - "WARNING: Asked for %s eigenvalues but max possible is %s.", self._in_k, self._k - ) - else: - self._k = self._in_k - - def _solve(self, operator: BaseOperator | PauliSumOp) -> tuple[np.ndarray, np.ndarray]: - if isinstance(operator, PauliSumOp): - op_matrix = operator.to_spmatrix() - else: - try: - op_matrix = operator.to_matrix(sparse=True) - except TypeError: - logger.debug( - "WARNING: operator of type `%s` does not support sparse matrices. " - "Trying dense computation", - type(operator), - ) - try: - op_matrix = operator.to_matrix() - except AttributeError as ex: - raise AlgorithmError(f"Unsupported operator type `{type(operator)}`.") from ex - - if isinstance(op_matrix, scisparse.csr_matrix): - # If matrix is diagonal, the elements on the diagonal are the eigenvalues. Solve by sorting. - if scisparse.csr_matrix(op_matrix.diagonal()).nnz == op_matrix.nnz: - diag = op_matrix.diagonal() - indices = np.argsort(diag)[: self._k] - eigval = diag[indices] - eigvec = np.zeros((op_matrix.shape[0], self._k)) - for i, idx in enumerate(indices): - eigvec[idx, i] = 1.0 - else: - if self._k >= 2**operator.num_qubits - 1: - logger.debug( - "SciPy doesn't support to get all eigenvalues, using NumPy instead." - ) - eigval, eigvec = self._solve_dense(operator.to_matrix()) - else: - eigval, eigvec = self._solve_sparse(op_matrix, self._k) - else: - # Sparse SciPy matrix not supported, use dense NumPy computation. - eigval, eigvec = self._solve_dense(operator.to_matrix()) - - indices = np.argsort(eigval)[: self._k] - eigval = eigval[indices] - eigvec = eigvec[:, indices] - return eigval, eigvec.T - - @staticmethod - def _solve_sparse(op_matrix: scisparse.csr_matrix, k: int) -> tuple[np.ndarray, np.ndarray]: - if (op_matrix != op_matrix.H).nnz == 0: - # Operator is Hermitian - return scisparse.linalg.eigsh(op_matrix, k=k, which="SA") - else: - return scisparse.linalg.eigs(op_matrix, k=k, which="SR") - - @staticmethod - def _solve_dense(op_matrix: np.ndarray) -> tuple[np.ndarray, np.ndarray]: - if op_matrix.all() == op_matrix.conj().T.all(): - # Operator is Hermitian - return np.linalg.eigh(op_matrix) - else: - return np.linalg.eig(op_matrix) - - @staticmethod - def _eval_aux_operators( - aux_operators: ListOrDict[BaseOperator | PauliSumOp], - wavefn: np.ndarray, - threshold: float = 1e-12, - ) -> ListOrDict[tuple[complex, complex]]: - - values: ListOrDict[tuple[complex, complex]] - - # As a list, aux_operators can contain None operators for which None values are returned. - # As a dict, the None operators in aux_operators have been dropped in compute_eigenvalues. - if isinstance(aux_operators, list): - values = [None] * len(aux_operators) - key_op_iterator = enumerate(aux_operators) - else: - values = {} - key_op_iterator = aux_operators.items() - - for key, operator in key_op_iterator: - if operator is None: - continue - - if operator.num_qubits is None or operator.num_qubits < 1: - logger.info( - "The number of qubits of the %s operator must be greater than zero.", key - ) - continue - - op_matrix = None - if isinstance(operator, PauliSumOp): - if operator.coeff != 0: - op_matrix = operator.to_spmatrix() - else: - try: - op_matrix = operator.to_matrix(sparse=True) - except TypeError: - logger.debug( - "WARNING: operator of type `%s` does not support sparse matrices. " - "Trying dense computation", - type(operator), - ) - try: - op_matrix = operator.to_matrix() - except AttributeError as ex: - raise AlgorithmError(f"Unsupported operator type {type(operator)}.") from ex - - if isinstance(op_matrix, scisparse.csr_matrix): - value = op_matrix.dot(wavefn).dot(np.conj(wavefn)) - elif isinstance(op_matrix, np.ndarray): - value = Statevector(wavefn).expectation_value(operator) - else: - value = 0.0 - - value = value if np.abs(value) > threshold else 0.0 - # The value gets wrapped into a tuple: (mean, metadata). - # The metadata includes variance (and, for other eigensolvers, shots). - # Since this is an exact computation, there are no shots - # and the variance is known to be zero. - values[key] = (value, {"variance": 0.0}) - return values - - def compute_eigenvalues( - self, - operator: BaseOperator | PauliSumOp, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - ) -> NumPyEigensolverResult: - - super().compute_eigenvalues(operator, aux_operators) - - if operator.num_qubits is None or operator.num_qubits < 1: - raise AlgorithmError("The number of qubits of the operator must be greater than zero.") - - self._check_set_k(operator) - - zero_op = SparsePauliOp(["I" * operator.num_qubits], coeffs=[0.0]) - if isinstance(aux_operators, list) and len(aux_operators) > 0: - # For some reason Chemistry passes aux_ops with 0 qubits and paulis sometimes. - aux_operators = [zero_op if op == 0 else op for op in aux_operators] - elif isinstance(aux_operators, dict) and len(aux_operators) > 0: - aux_operators = { - key: zero_op if op == 0 else op # Convert zero values to zero operators - for key, op in aux_operators.items() - if op is not None # Discard None values - } - else: - aux_operators = None - - k_orig = self._k - if self._filter_criterion: - # need to consider all elements if a filter is set - self._k = 2**operator.num_qubits - - eigvals, eigvecs = self._solve(operator) - - # compute energies before filtering, as this also evaluates the aux operators - if aux_operators is not None: - aux_op_vals = [ - self._eval_aux_operators(aux_operators, eigvecs[i]) for i in range(self._k) - ] - else: - aux_op_vals = None - - # if a filter is set, loop over the given values and only keep - if self._filter_criterion: - filt_eigvals = [] - filt_eigvecs = [] - filt_aux_op_vals = [] - count = 0 - for i, (eigval, eigvec) in enumerate(zip(eigvals, eigvecs)): - if aux_op_vals is not None: - aux_op_val = aux_op_vals[i] - else: - aux_op_val = None - - if self._filter_criterion(eigvec, eigval, aux_op_val): - count += 1 - filt_eigvecs.append(eigvec) - filt_eigvals.append(eigval) - if aux_op_vals is not None: - filt_aux_op_vals.append(aux_op_val) - - if count == k_orig: - break - - eigvals = np.array(filt_eigvals) - eigvecs = np.array(filt_eigvecs) - aux_op_vals = filt_aux_op_vals - - self._k = k_orig - - result = NumPyEigensolverResult() - result.eigenvalues = eigvals - result.eigenstates = [Statevector(vec) for vec in eigvecs] - result.aux_operators_evaluated = aux_op_vals - - logger.debug("NumpyEigensolverResult:\n%s", result) - return result - - -class NumPyEigensolverResult(EigensolverResult): - """NumPy eigensolver result.""" - - def __init__(self) -> None: - super().__init__() - self._eigenstates: list[Statevector] | None = None - - @property - def eigenstates(self) -> list[Statevector] | None: - """Return eigenstates.""" - return self._eigenstates - - @eigenstates.setter - def eigenstates(self, value: list[Statevector]) -> None: - """Set eigenstates.""" - self._eigenstates = value diff --git a/qiskit/algorithms/eigensolvers/vqd.py b/qiskit/algorithms/eigensolvers/vqd.py deleted file mode 100644 index 59f07d8a918b..000000000000 --- a/qiskit/algorithms/eigensolvers/vqd.py +++ /dev/null @@ -1,542 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Variational Quantum Deflation Algorithm for computing higher energy states. - -See https://arxiv.org/abs/1805.08138. -""" - -from __future__ import annotations - -from collections.abc import Callable, Sequence -from typing import Any -import logging -from time import time - -import numpy as np - -from qiskit.algorithms.state_fidelities import BaseStateFidelity -from qiskit.circuit import QuantumCircuit -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseEstimator -from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.quantum_info import SparsePauliOp - -from ..list_or_dict import ListOrDict -from ..optimizers import Optimizer, Minimizer, OptimizerResult -from ..variational_algorithm import VariationalAlgorithm -from .eigensolver import Eigensolver, EigensolverResult -from ..utils import validate_bounds, validate_initial_point -from ..exceptions import AlgorithmError -from ..observables_evaluator import estimate_observables - -# private function as we expect this to be updated in the next release -from ..utils.set_batching import _set_default_batchsize - -logger = logging.getLogger(__name__) - - -class VQD(VariationalAlgorithm, Eigensolver): - r"""The Variational Quantum Deflation algorithm. Implementation using primitives. - - `VQD `__ is a quantum algorithm that uses a - variational technique to find - the k eigenvalues of the Hamiltonian :math:`H` of a given system. - - The algorithm computes excited state energies of generalised hamiltonians - by optimising over a modified cost function where each succesive eigenvalue - is calculated iteratively by introducing an overlap term with all - the previously computed eigenstates that must be minimised, thus ensuring - higher energy eigenstates are found. - - An instance of VQD requires defining three algorithmic sub-components: - an integer k denoting the number of eigenstates to calculate, a trial - state (a.k.a. ansatz) which is a :class:`QuantumCircuit`, - and one instance (or list of) classical :mod:`~qiskit.algorithms.optimizers`. - The optimizer varies the circuit parameters - The trial state :math:`|\psi(\vec\theta)\rangle` is varied by the optimizer, - which modifies the set of ansatz parameters :math:`\vec\theta` - such that the expectation value of the operator on the corresponding - state approaches a minimum. The algorithm does this by iteratively refining - each excited state to be orthogonal to all the previous excited states. - - An optional array of parameter values, via the *initial_point*, may be provided - as the starting point for the search of the minimum eigenvalue. This feature is - particularly useful when there are reasons to believe that the solution point - is close to a particular point. - - The length of the *initial_point* list value must match the number of the parameters - expected by the ansatz. If the *initial_point* is left at the default - of ``None``, then VQD will look to the ansatz for a preferred value, based on its - given initial state. If the ansatz returns ``None``, - then a random point will be generated within the parameter bounds set, as per above. - It is also possible to give a list of initial points, one for every kth eigenvalue. - If the ansatz provides ``None`` as the lower bound, then VQD - will default it to :math:`-2\pi`; similarly, if the ansatz returns ``None`` - as the upper bound, the default value will be :math:`2\pi`. - - The following attributes can be set via the initializer but can also be read and - updated once the VQD object has been constructed. - - Attributes: - estimator (BaseEstimator): The primitive instance used to perform the expectation - estimation as indicated in the VQD paper. - fidelity (BaseStateFidelity): The fidelity class instance used to compute the - overlap estimation as indicated in the VQD paper. - ansatz (QuantumCircuit): A parameterized circuit used as ansatz for the wave function. - optimizer(Optimizer | Sequence[Optimizer]): A classical optimizer or a list of optimizers, - one for every k-th eigenvalue. Can either be a Qiskit optimizer or a callable - that takes an array as input and returns a Qiskit or SciPy optimization result. - k (int): the number of eigenvalues to return. Returns the lowest k eigenvalues. - betas (list[float]): Beta parameters in the VQD paper. - Should have length k - 1, with k the number of excited states. - These hyper-parameters balance the contribution of each overlap term to the cost - function and have a default value computed as the mean square sum of the - coefficients of the observable. - initial point (Sequence[float] | Sequence[Sequence[float]] | None): An optional initial - point (i.e. initial parameter values) or a list of initial points - (one for every k-th eigenvalue) for the optimizer. - If ``None`` then VQD will look to the ansatz for a - preferred point and if not will simply compute a random one. - callback (Callable[[int, np.ndarray, float, dict[str, Any]], None] | None): - A callback that can access the intermediate data - during the optimization. Four parameter values are passed to the callback as - follows during each evaluation by the optimizer: the evaluation count, - the optimizer parameters for the ansatz, the estimated value, the estimation - metadata, and the current step. - """ - - def __init__( - self, - estimator: BaseEstimator, - fidelity: BaseStateFidelity, - ansatz: QuantumCircuit, - optimizer: Optimizer | Minimizer | Sequence[Optimizer | Minimizer], - *, - k: int = 2, - betas: Sequence[float] | None = None, - initial_point: Sequence[float] | Sequence[Sequence[float]] | None = None, - callback: Callable[[int, np.ndarray, float, dict[str, Any]], None] | None = None, - ) -> None: - """ - - Args: - estimator: The estimator primitive. - fidelity: The fidelity class using primitives. - ansatz: A parameterized circuit used as ansatz for the wave function. - optimizer: A classical optimizer or a list of optimizers, one for every k-th eigenvalue. - Can either be a Qiskit optimizer or a callable - that takes an array as input and returns a Qiskit or SciPy optimization result. - k: The number of eigenvalues to return. Returns the lowest k eigenvalues. - betas: Beta parameters in the VQD paper. - Should have length k - 1, with k the number of excited states. - These hyperparameters balance the contribution of each overlap term to the cost - function and have a default value computed as the mean square sum of the - coefficients of the observable. - initial_point: An optional initial point (i.e. initial parameter values) - or a list of initial points (one for every k-th eigenvalue) - for the optimizer. - If ``None`` then VQD will look to the ansatz for a preferred - point and if not will simply compute a random one. - callback: A callback that can access the intermediate data - during the optimization. Four parameter values are passed to the callback as - follows during each evaluation by the optimizer: the evaluation count, - the optimizer parameters for the ansatz, the estimated value, - the estimation metadata, and the current step. - """ - super().__init__() - - self.estimator = estimator - self.fidelity = fidelity - self.ansatz = ansatz - self.optimizer = optimizer - self.k = k - self.betas = betas - # this has to go via getters and setters due to the VariationalAlgorithm interface - self.initial_point = initial_point - self.callback = callback - - self._eval_count = 0 - - @property - def initial_point(self) -> Sequence[float] | Sequence[Sequence[float]] | None: - """Returns initial point.""" - return self._initial_point - - @initial_point.setter - def initial_point(self, initial_point: Sequence[float] | Sequence[Sequence[float]] | None): - """Sets initial point""" - self._initial_point = initial_point - - def _check_operator_ansatz(self, operator: BaseOperator | PauliSumOp): - """Check that the number of qubits of operator and ansatz match.""" - if operator is not None and self.ansatz is not None: - if operator.num_qubits != self.ansatz.num_qubits: - # try to set the number of qubits on the ansatz, if possible - try: - self.ansatz.num_qubits = operator.num_qubits - except AttributeError as exc: - raise AlgorithmError( - "The number of qubits of the ansatz does not match the " - "operator, and the ansatz does not allow setting the " - "number of qubits using `num_qubits`." - ) from exc - - @classmethod - def supports_aux_operators(cls) -> bool: - return True - - def compute_eigenvalues( - self, - operator: BaseOperator | PauliSumOp, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - ) -> VQDResult: - super().compute_eigenvalues(operator, aux_operators) - - # this sets the size of the ansatz, so it must be called before the initial point - # validation - self._check_operator_ansatz(operator) - - bounds = validate_bounds(self.ansatz) - - # We need to handle the array entries being zero or Optional i.e. having value None - if aux_operators: - zero_op = SparsePauliOp.from_list([("I" * self.ansatz.num_qubits, 0)]) - - # Convert the None and zero values when aux_operators is a list. - # Drop None and convert zero values when aux_operators is a dict. - if isinstance(aux_operators, list): - key_op_iterator = enumerate(aux_operators) - converted: ListOrDict[BaseOperator | PauliSumOp] = [zero_op] * len(aux_operators) - else: - key_op_iterator = aux_operators.items() - converted = {} - for key, op in key_op_iterator: - if op is not None: - converted[key] = zero_op if op == 0 else op - - aux_operators = converted - - else: - aux_operators = None - - if self.betas is None: - if isinstance(operator, PauliSumOp): - operator = operator.coeff * operator.primitive - - try: - upper_bound = sum(np.abs(operator.coeffs)) - - except Exception as exc: - raise NotImplementedError( - r"Beta autoevaluation is not supported for operators" - f"of type {type(operator)}." - ) from exc - - betas = [upper_bound * 10] * (self.k) - logger.info("beta autoevaluated to %s", betas[0]) - else: - betas = self.betas - - result = self._build_vqd_result() - - if aux_operators is not None: - aux_values = [] - - # We keep a list of the bound circuits with optimal parameters, to avoid re-binding - # the same parameters to the ansatz if we do multiple steps - prev_states = [] - - num_initial_points = 0 - if self.initial_point is not None: - initial_points = np.reshape(self.initial_point, (-1, self.ansatz.num_parameters)) - num_initial_points = len(initial_points) - - # 0 just means the initial point is ``None`` and ``validate_initial_point`` - # will select a random point - if num_initial_points <= 1: - initial_point = validate_initial_point(self.initial_point, self.ansatz) - - for step in range(1, self.k + 1): - if num_initial_points > 1: - initial_point = validate_initial_point(initial_points[step - 1], self.ansatz) - - if step > 1: - prev_states.append(self.ansatz.assign_parameters(result.optimal_points[-1])) - - self._eval_count = 0 - energy_evaluation = self._get_evaluate_energy( - step, operator, betas, prev_states=prev_states - ) - - start_time = time() - - # TODO: add gradient support after FidelityGradients are implemented - if isinstance(self.optimizer, Sequence): - optimizer = self.optimizer[step - 1] - else: - optimizer = self.optimizer # fall back to single optimizer if not list - - if callable(optimizer): - opt_result = optimizer( # pylint: disable=not-callable - fun=energy_evaluation, x0=initial_point, bounds=bounds - ) - else: - # we always want to submit as many estimations per job as possible for minimal - # overhead on the hardware - was_updated = _set_default_batchsize(optimizer) - - opt_result = optimizer.minimize( - fun=energy_evaluation, x0=initial_point, bounds=bounds - ) - - # reset to original value - if was_updated: - optimizer.set_max_evals_grouped(None) - - eval_time = time() - start_time - - self._update_vqd_result(result, opt_result, eval_time, self.ansatz.copy()) - - if aux_operators is not None: - aux_value = estimate_observables( - self.estimator, self.ansatz, aux_operators, result.optimal_points[-1] - ) - aux_values.append(aux_value) - - if step == 1: - logger.info( - "Ground state optimization complete in %s seconds.\n" - "Found opt_params %s in %s evals", - eval_time, - result.optimal_points, - self._eval_count, - ) - else: - logger.info( - ( - "%s excited state optimization complete in %s s.\n" - "Found opt_params %s in %s evals" - ), - str(step - 1), - eval_time, - result.optimal_points, - self._eval_count, - ) - - # To match the signature of EigensolverResult - result.eigenvalues = np.array(result.eigenvalues) - - if aux_operators is not None: - result.aux_operators_evaluated = aux_values - - return result - - def _get_evaluate_energy( - self, - step: int, - operator: BaseOperator | PauliSumOp, - betas: Sequence[float], - prev_states: list[QuantumCircuit] | None = None, - ) -> Callable[[np.ndarray], float | np.ndarray]: - """Returns a function handle to evaluate the ansatz's energy for any given parameters. - This is the objective function to be passed to the optimizer that is used for evaluation. - - Args: - step: level of energy being calculated. 0 for ground, 1 for first excited state... - operator: The operator whose energy to evaluate. - betas: Beta parameters in the VQD paper. - prev_states: List of optimal circuits from previous rounds of optimization. - - Returns: - A callable that computes and returns the energy of the hamiltonian - of each parameter. - - Raises: - AlgorithmError: If the circuit is not parameterized (i.e. has 0 free parameters). - AlgorithmError: If operator was not provided. - RuntimeError: If the previous states array is of the wrong size. - """ - - num_parameters = self.ansatz.num_parameters - if num_parameters == 0: - raise AlgorithmError("The ansatz must be parameterized, but has no free parameters.") - - if step > 1 and (len(prev_states) + 1) != step: - raise RuntimeError( - f"Passed previous states of the wrong size." - f"Passed array has length {str(len(prev_states))}" - ) - - self._check_operator_ansatz(operator) - - def evaluate_energy(parameters: np.ndarray) -> float | np.ndarray: - # handle broadcasting: ensure parameters is of shape [array, array, ...] - if len(parameters.shape) == 1: - parameters = np.reshape(parameters, (-1, num_parameters)) - batch_size = len(parameters) - - estimator_job = self.estimator.run( - batch_size * [self.ansatz], batch_size * [operator], parameters - ) - - total_cost = np.zeros(batch_size) - - if step > 1: - # compute overlap cost - batched_prev_states = [state for state in prev_states for _ in range(batch_size)] - fidelity_job = self.fidelity.run( - batch_size * [self.ansatz] * (step - 1), - batched_prev_states, - np.tile(parameters, (step - 1, 1)), - ) - costs = fidelity_job.result().fidelities - - costs = np.reshape(costs, (step - 1, -1)) - for state, cost in enumerate(costs): - total_cost += np.real(betas[state] * cost) - - try: - estimator_result = estimator_job.result() - - except Exception as exc: - raise AlgorithmError("The primitive job to evaluate the energy failed!") from exc - - values = estimator_result.values + total_cost - - if self.callback is not None: - metadata = estimator_result.metadata - for params, value, meta in zip(parameters, values, metadata): - self._eval_count += 1 - self.callback(self._eval_count, params, value, meta, step) - else: - self._eval_count += len(values) - - return values if len(values) > 1 else values[0] - - return evaluate_energy - - @staticmethod - def _build_vqd_result() -> VQDResult: - result = VQDResult() - result.optimal_points = np.array([]) - result.optimal_parameters = [] - result.optimal_values = np.array([]) - result.cost_function_evals = np.array([], dtype=int) - result.optimizer_times = np.array([]) - result.eigenvalues = [] - result.optimizer_results = [] - result.optimal_circuits = [] - return result - - @staticmethod - def _update_vqd_result( - result: VQDResult, opt_result: OptimizerResult, eval_time, ansatz - ) -> VQDResult: - result.optimal_points = ( - np.concatenate([result.optimal_points, [opt_result.x]]) - if len(result.optimal_points) > 0 - else np.array([opt_result.x]) - ) - result.optimal_parameters.append(dict(zip(ansatz.parameters, opt_result.x))) - result.optimal_values = np.concatenate([result.optimal_values, [opt_result.fun]]) - result.cost_function_evals = np.concatenate([result.cost_function_evals, [opt_result.nfev]]) - result.optimizer_times = np.concatenate([result.optimizer_times, [eval_time]]) - result.eigenvalues.append(opt_result.fun + 0j) - result.optimizer_results.append(opt_result) - result.optimal_circuits.append(ansatz) - return result - - -class VQDResult(EigensolverResult): - """VQD Result.""" - - def __init__(self) -> None: - super().__init__() - - self._cost_function_evals: np.ndarray | None = None - self._optimizer_times: np.ndarray | None = None - self._optimal_values: np.ndarray | None = None - self._optimal_points: np.ndarray | None = None - self._optimal_parameters: list[dict] | None = None - self._optimizer_results: list[OptimizerResult] | None = None - self._optimal_circuits: list[QuantumCircuit] | None = None - - @property - def cost_function_evals(self) -> np.ndarray | None: - """Returns number of cost optimizer evaluations""" - return self._cost_function_evals - - @cost_function_evals.setter - def cost_function_evals(self, value: np.ndarray) -> None: - """Sets number of cost function evaluations""" - self._cost_function_evals = value - - @property - def optimizer_times(self) -> np.ndarray | None: - """Returns time taken for optimization for each step""" - return self._optimizer_times - - @optimizer_times.setter - def optimizer_times(self, value: np.ndarray) -> None: - """Sets time taken for optimization for each step""" - self._optimizer_times = value - - @property - def optimal_values(self) -> np.ndarray | None: - """Returns optimal value for each step""" - return self._optimal_values - - @optimal_values.setter - def optimal_values(self, value: np.ndarray) -> None: - """Sets optimal values""" - self._optimal_values = value - - @property - def optimal_points(self) -> np.ndarray | None: - """Returns optimal point for each step""" - return self._optimal_points - - @optimal_points.setter - def optimal_points(self, value: np.ndarray) -> None: - """Sets optimal points""" - self._optimal_points = value - - @property - def optimal_parameters(self) -> list[dict] | None: - """Returns the optimal parameters for each step""" - return self._optimal_parameters - - @optimal_parameters.setter - def optimal_parameters(self, value: list[dict]) -> None: - """Sets optimal parameters""" - self._optimal_parameters = value - - @property - def optimizer_results(self) -> list[OptimizerResult] | None: - """Returns the optimizer results for each step""" - return self._optimizer_results - - @optimizer_results.setter - def optimizer_results(self, value: list[OptimizerResult]) -> None: - """Sets optimizer results""" - self._optimizer_results = value - - @property - def optimal_circuits(self) -> list[QuantumCircuit] | None: - """The optimal circuits. Along with the optimal parameters, - these can be used to retrieve the different eigenstates.""" - return self._optimal_circuits - - @optimal_circuits.setter - def optimal_circuits(self, optimal_circuits: list[QuantumCircuit]) -> None: - self._optimal_circuits = optimal_circuits diff --git a/qiskit/algorithms/evolvers/__init__.py b/qiskit/algorithms/evolvers/__init__.py deleted file mode 100644 index 990c787b7c15..000000000000 --- a/qiskit/algorithms/evolvers/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Quantum Time Evolution package.""" - -from .evolution_result import EvolutionResult -from .evolution_problem import EvolutionProblem - -__all__ = [ - "EvolutionResult", - "EvolutionProblem", -] diff --git a/qiskit/algorithms/evolvers/evolution_problem.py b/qiskit/algorithms/evolvers/evolution_problem.py deleted file mode 100644 index 8d87dbd9ae6a..000000000000 --- a/qiskit/algorithms/evolvers/evolution_problem.py +++ /dev/null @@ -1,122 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Evolution problem class.""" - -from __future__ import annotations - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter -from qiskit.opflow import OperatorBase, StateFn -from qiskit.utils.deprecation import deprecate_func -from ..list_or_dict import ListOrDict - - -class EvolutionProblem: - """Deprecated: Evolution problem class. - - The EvolutionProblem class has been superseded by the - :class:`qiskit.algorithms.time_evolvers.TimeEvolutionProblem` class. - This class will be deprecated in a future release and subsequently - removed after that. - - This class is the input to time evolution algorithms and must contain information on the total - evolution time, a quantum state to be evolved and under which Hamiltonian the state is evolved. - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.time_evolvers.TimeEvolutionProblem``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - hamiltonian: OperatorBase, - time: float, - initial_state: StateFn | QuantumCircuit | None = None, - aux_operators: ListOrDict[OperatorBase] | None = None, - truncation_threshold: float = 1e-12, - t_param: Parameter | None = None, - param_value_dict: dict[Parameter, complex] | None = None, - ): - """ - Args: - hamiltonian: The Hamiltonian under which to evolve the system. - time: Total time of evolution. - initial_state: The quantum state to be evolved for methods like Trotterization. - For variational time evolutions, where the evolution happens in an ansatz, - this argument is not required. - aux_operators: Optional list of auxiliary operators to be evaluated with the - evolved ``initial_state`` and their expectation values returned. - truncation_threshold: Defines a threshold under which values can be assumed to be 0. - Used when ``aux_operators`` is provided. - t_param: Time parameter in case of a time-dependent Hamiltonian. This - free parameter must be within the ``hamiltonian``. - param_value_dict: Maps free parameters in the problem to values. Depending on the - algorithm, it might refer to e.g. a Hamiltonian or an initial state. - - Raises: - ValueError: If non-positive time of evolution is provided. - """ - - self.t_param = t_param - self.param_value_dict = param_value_dict - self.hamiltonian = hamiltonian - self.time = time - self.initial_state = initial_state - self.aux_operators = aux_operators - self.truncation_threshold = truncation_threshold - - @property - def time(self) -> float: - """Returns time.""" - return self._time - - @time.setter - def time(self, time: float) -> None: - """ - Sets time and validates it. - - Raises: - ValueError: If time is not positive. - """ - if time <= 0: - raise ValueError(f"Evolution time must be > 0 but was {time}.") - self._time = time - - def validate_params(self) -> None: - """ - Checks if all parameters present in the Hamiltonian are also present in the dictionary - that maps them to values. - - Raises: - ValueError: If Hamiltonian parameters cannot be bound with data provided. - """ - if isinstance(self.hamiltonian, OperatorBase): - t_param_set = set() - if self.t_param is not None: - t_param_set.add(self.t_param) - hamiltonian_dict_param_set: set[Parameter] = set() - if self.param_value_dict is not None: - hamiltonian_dict_param_set = hamiltonian_dict_param_set.union( - set(self.param_value_dict.keys()) - ) - params_set = t_param_set.union(hamiltonian_dict_param_set) - hamiltonian_param_set = set(self.hamiltonian.parameters) - - if hamiltonian_param_set != params_set: - raise ValueError( - f"Provided parameters {params_set} do not match Hamiltonian parameters " - f"{hamiltonian_param_set}." - ) diff --git a/qiskit/algorithms/evolvers/evolution_result.py b/qiskit/algorithms/evolvers/evolution_result.py deleted file mode 100644 index 5dd9e103f669..000000000000 --- a/qiskit/algorithms/evolvers/evolution_result.py +++ /dev/null @@ -1,55 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Class for holding evolution result.""" - -from __future__ import annotations - -from qiskit import QuantumCircuit -from qiskit.algorithms.list_or_dict import ListOrDict -from qiskit.opflow import StateFn, OperatorBase -from qiskit.utils.deprecation import deprecate_func -from ..algorithm_result import AlgorithmResult - - -class EvolutionResult(AlgorithmResult): - """Deprecated: Class for holding evolution result. - - The EvolutionResult class has been superseded by the - :class:`qiskit.algorithms.time_evolvers.TimeEvolutionResult` class. - This class will be deprecated in a future release and subsequently - removed after that. - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.time_evolvers.TimeEvolutionResult``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - evolved_state: StateFn | QuantumCircuit | OperatorBase, - aux_ops_evaluated: ListOrDict[tuple[complex, complex]] | None = None, - ): - """ - Args: - evolved_state: An evolved quantum state. - aux_ops_evaluated: Optional list of observables for which expected values on an evolved - state are calculated. These values are in fact tuples formatted as (mean, standard - deviation). - """ - - self.evolved_state = evolved_state - self.aux_ops_evaluated = aux_ops_evaluated diff --git a/qiskit/algorithms/evolvers/imaginary_evolver.py b/qiskit/algorithms/evolvers/imaginary_evolver.py deleted file mode 100644 index 74b301d3e539..000000000000 --- a/qiskit/algorithms/evolvers/imaginary_evolver.py +++ /dev/null @@ -1,55 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Interface for Quantum Imaginary Time Evolution.""" - -from abc import ABC, abstractmethod - -from qiskit.utils.deprecation import deprecate_func -from .evolution_problem import EvolutionProblem -from .evolution_result import EvolutionResult - - -class ImaginaryEvolver(ABC): - """Deprecated: Interface for Quantum Imaginary Time Evolution. - - The ImaginaryEvolver interface has been superseded by the - :class:`qiskit.algorithms.time_evolvers.ImaginaryTimeEvolver` interface. - This interface will be deprecated in a future release and subsequently - removed after that. - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the interface ``qiskit.algorithms.time_evolvers.ImaginaryTimeEvolver``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__(self) -> None: - pass - - @abstractmethod - def evolve(self, evolution_problem: EvolutionProblem) -> EvolutionResult: - r"""Perform imaginary time evolution :math:`\exp(-\tau H)|\Psi\rangle`. - - Evolves an initial state :math:`|\Psi\rangle` for an imaginary time :math:`\tau` - under a Hamiltonian :math:`H`, as provided in the ``evolution_problem``. - - Args: - evolution_problem: The definition of the evolution problem. - - Returns: - Evolution result which includes an evolved quantum state. - """ - raise NotImplementedError() diff --git a/qiskit/algorithms/evolvers/real_evolver.py b/qiskit/algorithms/evolvers/real_evolver.py deleted file mode 100644 index 5024143b59b6..000000000000 --- a/qiskit/algorithms/evolvers/real_evolver.py +++ /dev/null @@ -1,55 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Interface for Quantum Real Time Evolution.""" - -from abc import ABC, abstractmethod -from qiskit.utils.deprecation import deprecate_func - -from .evolution_problem import EvolutionProblem -from .evolution_result import EvolutionResult - - -class RealEvolver(ABC): - """Deprecated: Interface for Quantum Real Time Evolution. - - The RealEvolver interface has been superseded by the - :class:`qiskit.algorithms.time_evolvers.RealTimeEvolver` interface. - This interface will be deprecated in a future release and subsequently - removed after that. - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the interface ``qiskit.algorithms.time_evolvers.RealTimeEvolver``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__(self) -> None: - pass - - @abstractmethod - def evolve(self, evolution_problem: EvolutionProblem) -> EvolutionResult: - r"""Perform real time evolution :math:`\exp(-i t H)|\Psi\rangle`. - - Evolves an initial state :math:`|\Psi\rangle` for a time :math:`t` - under a Hamiltonian :math:`H`, as provided in the ``evolution_problem``. - - Args: - evolution_problem: The definition of the evolution problem. - - Returns: - Evolution result which includes an evolved quantum state. - """ - raise NotImplementedError() diff --git a/qiskit/algorithms/evolvers/trotterization/__init__.py b/qiskit/algorithms/evolvers/trotterization/__init__.py deleted file mode 100644 index fe1b8d8aedf2..000000000000 --- a/qiskit/algorithms/evolvers/trotterization/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""This package contains Trotterization-based Quantum Real Time Evolution algorithm. -It is compliant with the new Quantum Time Evolution Framework and makes use of -:class:`qiskit.synthesis.evolution.ProductFormula` and -:class:`~qiskit.circuit.library.PauliEvolutionGate` implementations. """ - -from qiskit.algorithms.evolvers.trotterization.trotter_qrte import ( - TrotterQRTE, -) - -__all__ = ["TrotterQRTE"] diff --git a/qiskit/algorithms/evolvers/trotterization/trotter_qrte.py b/qiskit/algorithms/evolvers/trotterization/trotter_qrte.py deleted file mode 100644 index 538635c67f42..000000000000 --- a/qiskit/algorithms/evolvers/trotterization/trotter_qrte.py +++ /dev/null @@ -1,262 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""An algorithm to implement a Trotterization real time-evolution.""" - -from __future__ import annotations - -import warnings - -from qiskit import QuantumCircuit -from qiskit.algorithms.aux_ops_evaluator import eval_observables -from qiskit.algorithms.evolvers import EvolutionProblem, EvolutionResult -from qiskit.algorithms.evolvers.real_evolver import RealEvolver -from qiskit.opflow import ( - SummedOp, - PauliOp, - CircuitOp, - ExpectationBase, - CircuitSampler, - PauliSumOp, - StateFn, - OperatorBase, -) -from qiskit.circuit.library import PauliEvolutionGate -from qiskit.providers import Backend -from qiskit.synthesis import ProductFormula, LieTrotter -from qiskit.utils import QuantumInstance -from qiskit.utils.deprecation import deprecate_func - - -class TrotterQRTE(RealEvolver): - """Deprecated: Quantum Real Time Evolution using Trotterization. - - The TrotterQRTE class has been superseded by the - :class:`qiskit.algorithms.time_evolvers.trotterization.TrotterQRTE` class. - This class will be deprecated in a future release and subsequently - removed after that. - - Type of Trotterization is defined by a ProductFormula provided. - - Examples:: - - from qiskit.opflow import X, Z, Zero - from qiskit.algorithms import EvolutionProblem, TrotterQRTE - from qiskit import BasicAer - from qiskit.utils import QuantumInstance - - operator = X + Z - initial_state = Zero - time = 1 - evolution_problem = EvolutionProblem(operator, 1, initial_state) - # LieTrotter with 1 rep - backend = BasicAer.get_backend("statevector_simulator") - quantum_instance = QuantumInstance(backend=backend) - trotter_qrte = TrotterQRTE(quantum_instance=quantum_instance) - evolved_state = trotter_qrte.evolve(evolution_problem).evolved_state - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.time_evolvers.trotterization.TrotterQRTE``." - " See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - product_formula: ProductFormula | None = None, - expectation: ExpectationBase | None = None, - quantum_instance: QuantumInstance | Backend | None = None, - ) -> None: - """ - Args: - product_formula: A Lie-Trotter-Suzuki product formula. The default is the Lie-Trotter - first order product formula with a single repetition. - expectation: An instance of ExpectationBase which defines a method for calculating - expectation values of EvolutionProblem.aux_operators. - quantum_instance: A quantum instance used for calculating expectation values of - EvolutionProblem.aux_operators. - """ - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - super().__init__() - if product_formula is None: - product_formula = LieTrotter() - self._product_formula = product_formula - self._quantum_instance = None - self._circuit_sampler: CircuitSampler | None = None - if quantum_instance is not None: - self.quantum_instance = quantum_instance - self._expectation = expectation - - @property - def product_formula(self) -> ProductFormula: - """Returns a product formula used in the algorithm.""" - return self._product_formula - - @product_formula.setter - def product_formula(self, product_formula: ProductFormula) -> None: - """ - Sets a product formula. - Args: - product_formula: A formula that defines the Trotterization algorithm. - """ - self._product_formula = product_formula - - @property - def quantum_instance(self) -> QuantumInstance | None: - """Returns a quantum instance used in the algorithm.""" - return self._quantum_instance - - @quantum_instance.setter - def quantum_instance(self, quantum_instance: QuantumInstance | Backend | None) -> None: - """ - Sets a quantum instance and a circuit sampler. - Args: - quantum_instance: The quantum instance used to run this algorithm. - """ - if isinstance(quantum_instance, Backend): - quantum_instance = QuantumInstance(quantum_instance) - - self._circuit_sampler = None - if quantum_instance is not None: - self._circuit_sampler = CircuitSampler(quantum_instance) - - self._quantum_instance = quantum_instance - - @property - def expectation(self) -> ExpectationBase | None: - """Returns an expectation used in the algorithm.""" - return self._expectation - - @expectation.setter - def expectation(self, expectation: ExpectationBase | None) -> None: - """ - Sets an expectation. - Args: - expectation: An instance of ExpectationBase which defines a method for calculating - expectation values of EvolutionProblem.aux_operators. - """ - self._expectation = expectation - - @classmethod - def supports_aux_operators(cls) -> bool: - """ - Whether computing the expectation value of auxiliary operators is supported. - - Returns: - True if ``aux_operators`` expectations in the EvolutionProblem can be evaluated, False - otherwise. - """ - return True - - def evolve(self, evolution_problem: EvolutionProblem) -> EvolutionResult: - """ - Evolves a quantum state for a given time using the Trotterization method - based on a product formula provided. The result is provided in the form of a quantum - circuit. If auxiliary operators are included in the ``evolution_problem``, they are - evaluated on an evolved state using a backend provided. - - .. note:: - Time-dependent Hamiltonians are not yet supported. - - Args: - evolution_problem: Instance defining evolution problem. For the included Hamiltonian, - ``PauliOp``, ``SummedOp`` or ``PauliSumOp`` are supported by TrotterQRTE. - - Returns: - Evolution result that includes an evolved state as a quantum circuit and, optionally, - auxiliary operators evaluated for a resulting state on a backend. - - Raises: - ValueError: If ``t_param`` is not set to None in the EvolutionProblem (feature not - currently supported). - ValueError: If the ``initial_state`` is not provided in the EvolutionProblem. - """ - evolution_problem.validate_params() - if evolution_problem.t_param is not None: - raise ValueError( - "TrotterQRTE does not accept a time dependent hamiltonian," - "``t_param`` from the EvolutionProblem should be set to None." - ) - - if evolution_problem.aux_operators is not None and ( - self._quantum_instance is None or self._expectation is None - ): - raise ValueError( - "aux_operators were provided for evaluations but no ``expectation`` or " - "``quantum_instance`` was provided." - ) - hamiltonian = evolution_problem.hamiltonian - if not isinstance(hamiltonian, (PauliOp, PauliSumOp, SummedOp)): - raise ValueError( - "TrotterQRTE only accepts PauliOp | " - f"PauliSumOp | SummedOp, {type(hamiltonian)} provided." - ) - if isinstance(hamiltonian, OperatorBase): - hamiltonian = hamiltonian.assign_parameters(evolution_problem.param_value_dict) - if isinstance(hamiltonian, SummedOp): - hamiltonian = self._summed_op_to_pauli_sum_op(hamiltonian) - # the evolution gate - evolution_gate = CircuitOp( - PauliEvolutionGate(hamiltonian, evolution_problem.time, synthesis=self._product_formula) - ) - - if evolution_problem.initial_state is not None: - initial_state = evolution_problem.initial_state - if isinstance(initial_state, QuantumCircuit): - initial_state = StateFn(initial_state) - evolved_state = evolution_gate @ initial_state - - else: - raise ValueError("``initial_state`` must be provided in the EvolutionProblem.") - - evaluated_aux_ops = None - if evolution_problem.aux_operators is not None: - evaluated_aux_ops = eval_observables( - self._quantum_instance, - evolved_state.primitive, - evolution_problem.aux_operators, - self._expectation, - evolution_problem.truncation_threshold, - ) - - return EvolutionResult(evolved_state, evaluated_aux_ops) - - @staticmethod - def _summed_op_to_pauli_sum_op( - hamiltonian: SummedOp, - ) -> PauliSumOp | PauliOp: - """ - Tries binding parameters in a Hamiltonian. - - Args: - hamiltonian: The Hamiltonian that defines an evolution. - - Returns: - Hamiltonian. - - Raises: - ValueError: If the ``SummedOp`` Hamiltonian contains operators of an invalid type. - """ - # PauliSumOp does not allow parametrized coefficients but after binding the parameters - # we need to convert it into a PauliSumOp for the PauliEvolutionGate. - op_list = [] - for op in hamiltonian.oplist: - if not isinstance(op, PauliOp): - raise ValueError( - "Content of the Hamiltonian not of type PauliOp. The " - f"following type detected: {type(op)}." - ) - op_list.append(op) - return sum(op_list) diff --git a/qiskit/algorithms/exceptions.py b/qiskit/algorithms/exceptions.py deleted file mode 100644 index 1f830a3cba95..000000000000 --- a/qiskit/algorithms/exceptions.py +++ /dev/null @@ -1,21 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Exception for errors raised by Algorithms module.""" - -from qiskit.exceptions import QiskitError - - -class AlgorithmError(QiskitError): - """For Algorithm specific errors.""" - - pass diff --git a/qiskit/algorithms/gradients/__init__.py b/qiskit/algorithms/gradients/__init__.py deleted file mode 100644 index ff3a2fceca5e..000000000000 --- a/qiskit/algorithms/gradients/__init__.py +++ /dev/null @@ -1,130 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023 -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -============================================== -Gradients (:mod:`qiskit.algorithms.gradients`) -============================================== - -.. currentmodule:: qiskit.algorithms.gradients - -Base Classes -============ - -.. autosummary:: - :toctree: ../stubs/ - - BaseEstimatorGradient - BaseQGT - BaseSamplerGradient - EstimatorGradientResult - SamplerGradientResult - QGTResult - -Finite Differences -================== - -.. autosummary:: - :toctree: ../stubs/ - - FiniteDiffEstimatorGradient - FiniteDiffSamplerGradient - -Linear Combination of Unitaries -=============================== - -.. autosummary:: - :toctree: ../stubs/ - - LinCombEstimatorGradient - LinCombSamplerGradient - LinCombQGT - -Parameter Shift Rules -===================== - -.. autosummary:: - :toctree: ../stubs/ - - ParamShiftEstimatorGradient - ParamShiftSamplerGradient - -Quantum Fisher Information -========================== - -.. autosummary:: - :toctree: ../stubs/ - - QFIResult - QFI - -Classical Methods -================= - -.. autosummary:: - :toctree: ../stubs/ - - ReverseEstimatorGradient - ReverseQGT - -Simultaneous Perturbation Stochastic Approximation -================================================== - -.. autosummary:: - :toctree: ../stubs/ - - SPSAEstimatorGradient - SPSASamplerGradient -""" - -from .base.base_estimator_gradient import BaseEstimatorGradient -from .base.base_qgt import BaseQGT -from .base.base_sampler_gradient import BaseSamplerGradient -from .base.estimator_gradient_result import EstimatorGradientResult -from .finite_diff.finite_diff_estimator_gradient import FiniteDiffEstimatorGradient -from .finite_diff.finite_diff_sampler_gradient import FiniteDiffSamplerGradient -from .lin_comb.lin_comb_estimator_gradient import DerivativeType, LinCombEstimatorGradient -from .lin_comb.lin_comb_qgt import LinCombQGT -from .lin_comb.lin_comb_sampler_gradient import LinCombSamplerGradient -from .param_shift.param_shift_estimator_gradient import ParamShiftEstimatorGradient -from .param_shift.param_shift_sampler_gradient import ParamShiftSamplerGradient -from .qfi import QFI -from .qfi_result import QFIResult -from .base.qgt_result import QGTResult -from .base.sampler_gradient_result import SamplerGradientResult -from .spsa.spsa_estimator_gradient import SPSAEstimatorGradient -from .spsa.spsa_sampler_gradient import SPSASamplerGradient -from .reverse.reverse_gradient import ReverseEstimatorGradient -from .reverse.reverse_qgt import ReverseQGT - -__all__ = [ - "BaseEstimatorGradient", - "BaseQGT", - "BaseSamplerGradient", - "DerivativeType", - "EstimatorGradientResult", - "FiniteDiffEstimatorGradient", - "FiniteDiffSamplerGradient", - "LinCombEstimatorGradient", - "LinCombQGT", - "LinCombSamplerGradient", - "ParamShiftEstimatorGradient", - "ParamShiftSamplerGradient", - "QFI", - "QFIResult", - "QGTResult", - "SamplerGradientResult", - "SPSAEstimatorGradient", - "SPSASamplerGradient", - "ReverseEstimatorGradient", - "ReverseQGT", -] diff --git a/qiskit/algorithms/gradients/base/__init__.py b/qiskit/algorithms/gradients/base/__init__.py deleted file mode 100644 index 8f5fd2a37f84..000000000000 --- a/qiskit/algorithms/gradients/base/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023 -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. diff --git a/qiskit/algorithms/gradients/base/base_estimator_gradient.py b/qiskit/algorithms/gradients/base/base_estimator_gradient.py deleted file mode 100644 index 0cbf478fa2ec..000000000000 --- a/qiskit/algorithms/gradients/base/base_estimator_gradient.py +++ /dev/null @@ -1,360 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023 -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Abstract base class of gradient for ``Estimator``. -""" - -from __future__ import annotations - -from abc import ABC, abstractmethod -from collections.abc import Sequence -from copy import copy - -import numpy as np - -from qiskit.circuit import Parameter, ParameterExpression, QuantumCircuit -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseEstimator -from qiskit.primitives.utils import _circuit_key -from qiskit.providers import Options -from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.transpiler.passes import TranslateParameterizedGates - -from .estimator_gradient_result import EstimatorGradientResult -from ..utils import ( - DerivativeType, - GradientCircuit, - _assign_unique_parameters, - _make_gradient_parameters, - _make_gradient_parameter_values, -) - -from ...algorithm_job import AlgorithmJob - - -class BaseEstimatorGradient(ABC): - """Base class for an ``EstimatorGradient`` to compute the gradients of the expectation value.""" - - def __init__( - self, - estimator: BaseEstimator, - options: Options | None = None, - derivative_type: DerivativeType = DerivativeType.REAL, - ): - r""" - Args: - estimator: The estimator used to compute the gradients. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting - derivative_type: The type of derivative. Can be either ``DerivativeType.REAL`` - ``DerivativeType.IMAG``, or ``DerivativeType.COMPLEX``. - - - ``DerivativeType.REAL`` computes :math:`2 \mathrm{Re}[⟨ψ(ω)|O(θ)|dω ψ(ω)〉]`. - - ``DerivativeType.IMAG`` computes :math:`2 \mathrm{Im}[⟨ψ(ω)|O(θ)|dω ψ(ω)〉]`. - - ``DerivativeType.COMPLEX`` computes :math:`2 ⟨ψ(ω)|O(θ)|dω ψ(ω)〉`. - - Defaults to ``DerivativeType.REAL``, as this yields e.g. the commonly-used energy - gradient and this type is the only supported type for function-level schemes like - finite difference. - """ - self._estimator: BaseEstimator = estimator - self._default_options = Options() - if options is not None: - self._default_options.update_options(**options) - self._derivative_type = derivative_type - - self._gradient_circuit_cache: dict[ - tuple, - GradientCircuit, - ] = {} - - @property - def derivative_type(self) -> DerivativeType: - """Return the derivative type (real, imaginary or complex). - - Returns: - The derivative type. - """ - return self._derivative_type - - def run( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter] | None] | None = None, - **options, - ) -> AlgorithmJob: - """Run the job of the estimator gradient on the given circuits. - - Args: - circuits: The list of quantum circuits to compute the gradients. - observables: The list of observables. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the gradients of - the specified parameters. Each sequence of parameters corresponds to a circuit in - ``circuits``. Defaults to None, which means that the gradients of all parameters in - each circuit are calculated. None in the sequence means that the gradients of all - parameters in the corresponding circuit are calculated. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting - - Returns: - The job object of the gradients of the expectation values. The i-th result corresponds to - ``circuits[i]`` evaluated with parameters bound as ``parameter_values[i]``. The j-th - element of the i-th result corresponds to the gradient of the i-th circuit with respect - to the j-th parameter. - - Raises: - ValueError: Invalid arguments are given. - """ - if isinstance(circuits, QuantumCircuit): - # Allow a single circuit to be passed in. - circuits = (circuits,) - if isinstance(observables, (BaseOperator, PauliSumOp)): - # Allow a single observable to be passed in. - observables = (observables,) - - if parameters is None: - # If parameters is None, we calculate the gradients of all parameters in each circuit. - parameters = [circuit.parameters for circuit in circuits] - else: - # If parameters is not None, we calculate the gradients of the specified parameters. - # None in parameters means that the gradients of all parameters in the corresponding - # circuit are calculated. - parameters = [ - params if params is not None else circuits[i].parameters - for i, params in enumerate(parameters) - ] - # Validate the arguments. - self._validate_arguments(circuits, observables, parameter_values, parameters) - # The priority of run option is as follows: - # options in ``run`` method > gradient's default options > primitive's default setting. - opts = copy(self._default_options) - opts.update_options(**options) - # Run the job. - job = AlgorithmJob( - self._run, circuits, observables, parameter_values, parameters, **opts.__dict__ - ) - job.submit() - return job - - @abstractmethod - def _run( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> EstimatorGradientResult: - """Compute the estimator gradients on the given circuits.""" - raise NotImplementedError() - - def _preprocess( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - supported_gates: Sequence[str], - ) -> tuple[Sequence[QuantumCircuit], Sequence[Sequence[float]], Sequence[Sequence[Parameter]]]: - """Preprocess the gradient. This makes a gradient circuit for each circuit. The gradient - circuit is a transpiled circuit by using the supported gates, and has unique parameters. - ``parameter_values`` and ``parameters`` are also updated to match the gradient circuit. - - Args: - circuits: The list of quantum circuits to compute the gradients. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the gradients of the specified - parameters. - supported_gates: The supported gates used to transpile the circuit. - - Returns: - The list of gradient circuits, the list of parameter values, and the list of parameters. - parameter_values and parameters are updated to match the gradient circuit. - """ - translator = TranslateParameterizedGates(supported_gates) - g_circuits, g_parameter_values, g_parameters = [], [], [] - for circuit, parameter_value_, parameters_ in zip(circuits, parameter_values, parameters): - circuit_key = _circuit_key(circuit) - if circuit_key not in self._gradient_circuit_cache: - unrolled = translator(circuit) - self._gradient_circuit_cache[circuit_key] = _assign_unique_parameters(unrolled) - gradient_circuit = self._gradient_circuit_cache[circuit_key] - g_circuits.append(gradient_circuit.gradient_circuit) - g_parameter_values.append( - _make_gradient_parameter_values(circuit, gradient_circuit, parameter_value_) - ) - g_parameters.append(_make_gradient_parameters(gradient_circuit, parameters_)) - return g_circuits, g_parameter_values, g_parameters - - def _postprocess( - self, - results: EstimatorGradientResult, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - ) -> EstimatorGradientResult: - """Postprocess the gradients. This method computes the gradient of the original circuits - by applying the chain rule to the gradient of the circuits with unique parameters. - - Args: - results: The computed gradients for the circuits with unique parameters. - circuits: The list of original circuits submitted for gradient computation. - parameter_values: The list of parameter values to be bound to the circuits. - parameters: The sequence of parameters to calculate only the gradients of the specified - parameters. - - Returns: - The gradients of the original circuits. - """ - gradients, metadata = [], [] - for idx, (circuit, parameter_values_, parameters_) in enumerate( - zip(circuits, parameter_values, parameters) - ): - gradient = np.zeros(len(parameters_)) - if ( - "derivative_type" in results.metadata[idx] - and results.metadata[idx]["derivative_type"] == DerivativeType.COMPLEX - ): - # If the derivative type is complex, cast the gradient to complex. - gradient = gradient.astype("complex") - gradient_circuit = self._gradient_circuit_cache[_circuit_key(circuit)] - g_parameters = _make_gradient_parameters(gradient_circuit, parameters_) - # Make a map from the gradient parameter to the respective index in the gradient. - g_parameter_indices = {param: i for i, param in enumerate(g_parameters)} - # Compute the original gradient from the gradient of the gradient circuit - # by using the chain rule. - for i, parameter in enumerate(parameters_): - for g_parameter, coeff in gradient_circuit.parameter_map[parameter]: - # Compute the coefficient - if isinstance(coeff, ParameterExpression): - local_map = { - p: parameter_values_[circuit.parameters.data.index(p)] - for p in coeff.parameters - } - bound_coeff = coeff.bind(local_map) - else: - bound_coeff = coeff - # The original gradient is a sum of the gradients of the parameters in the - # gradient circuit multiplied by the coefficients. - gradient[i] += ( - float(bound_coeff) - * results.gradients[idx][g_parameter_indices[g_parameter]] - ) - gradients.append(gradient) - metadata.append({"parameters": parameters_}) - return EstimatorGradientResult( - gradients=gradients, metadata=metadata, options=results.options - ) - - @staticmethod - def _validate_arguments( - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - ) -> None: - """Validate the arguments of the ``run`` method. - - Args: - circuits: The list of quantum circuits to compute the gradients. - observables: The list of observables. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the gradients of the specified - parameters. - - Raises: - ValueError: Invalid arguments are given. - """ - if len(circuits) != len(parameter_values): - raise ValueError( - f"The number of circuits ({len(circuits)}) does not match " - f"the number of parameter value sets ({len(parameter_values)})." - ) - - for i, (circuit, parameter_value) in enumerate(zip(circuits, parameter_values)): - if not circuit.num_parameters: - raise ValueError(f"The {i}-th circuit is not parameterised.") - if len(parameter_value) != circuit.num_parameters: - raise ValueError( - f"The number of values ({len(parameter_value)}) does not match " - f"the number of parameters ({circuit.num_parameters}) for the {i}-th circuit." - ) - - if len(circuits) != len(observables): - raise ValueError( - f"The number of circuits ({len(circuits)}) does not match " - f"the number of observables ({len(observables)})." - ) - - for i, (circuit, observable) in enumerate(zip(circuits, observables)): - if circuit.num_qubits != observable.num_qubits: - raise ValueError( - f"The number of qubits of the {i}-th circuit ({circuit.num_qubits}) does " - f"not match the number of qubits of the {i}-th observable " - f"({observable.num_qubits})." - ) - - if len(circuits) != len(parameters): - raise ValueError( - f"The number of circuits ({len(circuits)}) does not match " - f"the number of the list of specified parameters ({len(parameters)})." - ) - - for i, (circuit, parameters_) in enumerate(zip(circuits, parameters)): - if not set(parameters_).issubset(circuit.parameters): - raise ValueError( - f"The {i}-th parameters contains parameters not present in the " - f"{i}-th circuit." - ) - - @property - def options(self) -> Options: - """Return the union of estimator options setting and gradient default options, - where, if the same field is set in both, the gradient's default options override - the primitive's default setting. - - Returns: - The gradient default + estimator options. - """ - return self._get_local_options(self._default_options.__dict__) - - def update_default_options(self, **options): - """Update the gradient's default options setting. - - Args: - **options: The fields to update the default options. - """ - - self._default_options.update_options(**options) - - def _get_local_options(self, options: Options) -> Options: - """Return the union of the primitive's default setting, - the gradient default options, and the options in the ``run`` method. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - - Args: - options: The fields to update the options - - Returns: - The gradient default + estimator + run options. - """ - opts = copy(self._estimator.options) - opts.update_options(**options) - return opts diff --git a/qiskit/algorithms/gradients/base/base_qgt.py b/qiskit/algorithms/gradients/base/base_qgt.py deleted file mode 100644 index f2999a8f2bf0..000000000000 --- a/qiskit/algorithms/gradients/base/base_qgt.py +++ /dev/null @@ -1,383 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Abstract base class of the Quantum Geometric Tensor (QGT). -""" - -from __future__ import annotations - -from abc import ABC, abstractmethod -from collections.abc import Sequence -from copy import copy - -import numpy as np - -from qiskit.circuit import Parameter, ParameterExpression, QuantumCircuit -from qiskit.primitives import BaseEstimator -from qiskit.primitives.utils import _circuit_key -from qiskit.providers import Options -from qiskit.transpiler.passes import TranslateParameterizedGates - -from .qgt_result import QGTResult -from ..utils import ( - DerivativeType, - GradientCircuit, - _assign_unique_parameters, - _make_gradient_parameters, - _make_gradient_parameter_values, -) - -from ...algorithm_job import AlgorithmJob - - -class BaseQGT(ABC): - r"""Base class to computes the Quantum Geometric Tensor (QGT) given a pure, - parameterized quantum state. QGT is defined as: - - .. math:: - - \mathrm{QGT}_{ij}= \langle \partial_i \psi | \partial_j \psi \rangle - - \langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle. - """ - - def __init__( - self, - estimator: BaseEstimator, - phase_fix: bool = True, - derivative_type: DerivativeType = DerivativeType.COMPLEX, - options: Options | None = None, - ): - r""" - Args: - estimator: The estimator used to compute the QGT. - phase_fix: Whether to calculate the second term (phase fix) of the QGT, which is - :math:`\langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle`. - Defaults to ``True``. - derivative_type: The type of derivative. Can be either ``DerivativeType.REAL`` - ``DerivativeType.IMAG``, or ``DerivativeType.COMPLEX``. Defaults to - ``DerivativeType.REAL``. - - - ``DerivativeType.REAL`` computes - - .. math:: - - \mathrm{Re(QGT)}_{ij}= \mathrm{Re}[\langle \partial_i \psi | \partial_j \psi \rangle - - \langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle]. - - - ``DerivativeType.IMAG`` computes - - .. math:: - - \mathrm{Im(QGT)}_{ij}= \mathrm{Im}[\langle \partial_i \psi | \partial_j \psi \rangle - - \langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle]. - - - ``DerivativeType.COMPLEX`` computes - - .. math:: - - \mathrm{QGT}_{ij}= [\langle \partial_i \psi | \partial_j \psi \rangle - - \langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle]. - - options: Backend runtime options used for circuit execution. The order of priority is: - options in ``run`` method > QGT's default options > primitive's default - setting. Higher priority setting overrides lower priority setting. - """ - self._estimator: BaseEstimator = estimator - self._phase_fix: bool = phase_fix - self._derivative_type: DerivativeType = derivative_type - self._default_options = Options() - if options is not None: - self._default_options.update_options(**options) - self._qgt_circuit_cache: dict[tuple, GradientCircuit] = {} - self._gradient_circuit_cache: dict[tuple, GradientCircuit] = {} - - @property - def derivative_type(self) -> DerivativeType: - """The derivative type.""" - return self._derivative_type - - @derivative_type.setter - def derivative_type(self, derivative_type: DerivativeType) -> None: - """Set the derivative type.""" - self._derivative_type = derivative_type - - def run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter] | None] | None = None, - **options, - ) -> AlgorithmJob: - """Run the job of the QGTs on the given circuits. - - Args: - circuits: The list of quantum circuits to compute the QGTs. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the QGTs of - the specified parameters. Each sequence of parameters corresponds to a circuit in - ``circuits``. Defaults to None, which means that the QGTs of all parameters in - each circuit are calculated. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > QGT's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting. - - Returns: - The job object of the QGTs of the expectation values. The i-th result corresponds to - ``circuits[i]`` evaluated with parameters bound as ``parameter_values[i]``. - - Raises: - ValueError: Invalid arguments are given. - """ - if isinstance(circuits, QuantumCircuit): - # Allow a single circuit to be passed in. - circuits = (circuits,) - - if parameters is None: - # If parameters is None, we calculate the gradients of all parameters in each circuit. - parameters = [circuit.parameters for circuit in circuits] - else: - # If parameters is not None, we calculate the gradients of the specified parameters. - # None in parameters means that the gradients of all parameters in the corresponding - # circuit are calculated. - parameters = [ - params if params is not None else circuits[i].parameters - for i, params in enumerate(parameters) - ] - # Validate the arguments. - self._validate_arguments(circuits, parameter_values, parameters) - # The priority of run option is as follows: - # options in ``run`` method > QGT's default options > primitive's default setting. - opts = copy(self._default_options) - opts.update_options(**options) - job = AlgorithmJob(self._run, circuits, parameter_values, parameters, **opts.__dict__) - job.submit() - return job - - @abstractmethod - def _run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> QGTResult: - """Compute the QGTs on the given circuits.""" - raise NotImplementedError() - - def _preprocess( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - supported_gates: Sequence[str], - ) -> tuple[Sequence[QuantumCircuit], Sequence[Sequence[float]], Sequence[Sequence[Parameter]]]: - """Preprocess the gradient. This makes a gradient circuit for each circuit. The gradient - circuit is a transpiled circuit by using the supported gates, and has unique parameters. - ``parameter_values`` and ``parameters`` are also updated to match the gradient circuit. - - Args: - circuits: The list of quantum circuits to compute the gradients. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the gradients of the specified - parameters. - supported_gates: The supported gates used to transpile the circuit. - - Returns: - The list of gradient circuits, the list of parameter values, and the list of parameters. - parameter_values and parameters are updated to match the gradient circuit. - """ - translator = TranslateParameterizedGates(supported_gates) - g_circuits, g_parameter_values, g_parameters = [], [], [] - for circuit, parameter_value_, parameters_ in zip(circuits, parameter_values, parameters): - circuit_key = _circuit_key(circuit) - if circuit_key not in self._gradient_circuit_cache: - unrolled = translator(circuit) - self._gradient_circuit_cache[circuit_key] = _assign_unique_parameters(unrolled) - gradient_circuit = self._gradient_circuit_cache[circuit_key] - g_circuits.append(gradient_circuit.gradient_circuit) - g_parameter_values.append( - _make_gradient_parameter_values(circuit, gradient_circuit, parameter_value_) - ) - g_parameters_ = [ - g_param - for g_param in gradient_circuit.gradient_circuit.parameters - if g_param in _make_gradient_parameters(gradient_circuit, parameters_) - ] - g_parameters.append(g_parameters_) - return g_circuits, g_parameter_values, g_parameters - - def _postprocess( - self, - results: QGTResult, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - ) -> QGTResult: - """Postprocess the QGTs. This method computes the QGTs of the original circuits - by applying the chain rule to the QGTs of the circuits with unique parameters. - - Args: - results: The computed QGT for the circuits with unique parameters. - circuits: The list of original circuits submitted for gradient computation. - parameter_values: The list of parameter values to be bound to the circuits. - parameters: The sequence of parameters to calculate only the gradients of the specified - parameters. - - Returns: - The QGTs of the original circuits. - """ - qgts, metadata = [], [] - for idx, (circuit, parameter_values_, parameters_) in enumerate( - zip(circuits, parameter_values, parameters) - ): - dtype = complex if self.derivative_type == DerivativeType.COMPLEX else float - qgt: np.ndarray = np.zeros((len(parameters_), len(parameters_)), dtype=dtype) - - gradient_circuit = self._gradient_circuit_cache[_circuit_key(circuit)] - g_parameters = _make_gradient_parameters(gradient_circuit, parameters_) - # Make a map from the gradient parameter to the respective index in the gradient. - # parameters_ = [param for param in circuit.parameters if param in parameters_] - g_parameter_indices = [ - param - for param in gradient_circuit.gradient_circuit.parameters - if param in g_parameters - ] - g_parameter_indices = {param: i for i, param in enumerate(g_parameter_indices)} - rows, cols = np.triu_indices(len(parameters_)) - for row, col in zip(rows, cols): - for g_parameter1, coeff1 in gradient_circuit.parameter_map[parameters_[row]]: - for g_parameter2, coeff2 in gradient_circuit.parameter_map[parameters_[col]]: - if isinstance(coeff1, ParameterExpression): - local_map = { - p: parameter_values_[circuit.parameters.data.index(p)] - for p in coeff1.parameters - } - bound_coeff1 = coeff1.bind(local_map) - else: - bound_coeff1 = coeff1 - if isinstance(coeff2, ParameterExpression): - local_map = { - p: parameter_values_[circuit.parameters.data.index(p)] - for p in coeff2.parameters - } - bound_coeff2 = coeff2.bind(local_map) - else: - bound_coeff2 = coeff2 - qgt[row, col] += ( - float(bound_coeff1) - * float(bound_coeff2) - * results.qgts[idx][ - g_parameter_indices[g_parameter1], g_parameter_indices[g_parameter2] - ] - ) - - if self.derivative_type == DerivativeType.IMAG: - qgt += -1 * np.triu(qgt, k=1).T - else: - qgt += np.triu(qgt, k=1).conjugate().T - qgts.append(qgt) - metadata.append([{"parameters": parameters_}]) - return QGTResult( - qgts=qgts, - derivative_type=self.derivative_type, - metadata=metadata, - options=results.options, - ) - - @staticmethod - def _validate_arguments( - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - ) -> None: - """Validate the arguments of the ``run`` method. - - Args: - circuits: The list of quantum circuits to compute the QGTs. - parameter_values: The list of parameter values to be bound to the circuits. - parameters: The sequence of parameters with respect to which the QGTs should be - computed. - - Raises: - ValueError: Invalid arguments are given. - """ - if len(circuits) != len(parameter_values): - raise ValueError( - f"The number of circuits ({len(circuits)}) does not match " - f"the number of parameter values ({len(parameter_values)})." - ) - - if len(circuits) != len(parameters): - raise ValueError( - f"The number of circuits ({len(circuits)}) does not match " - f"the number of the specified parameter sets ({len(parameters)})." - ) - - for i, (circuit, parameter_value) in enumerate(zip(circuits, parameter_values)): - if not circuit.num_parameters: - raise ValueError(f"The {i}-th circuit is not parameterised.") - if len(parameter_value) != circuit.num_parameters: - raise ValueError( - f"The number of values ({len(parameter_value)}) does not match " - f"the number of parameters ({circuit.num_parameters}) for the {i}-th circuit." - ) - - if len(circuits) != len(parameters): - raise ValueError( - f"The number of circuits ({len(circuits)}) does not match " - f"the number of the list of specified parameters ({len(parameters)})." - ) - - for i, (circuit, parameters_) in enumerate(zip(circuits, parameters)): - if not set(parameters_).issubset(circuit.parameters): - raise ValueError( - f"The {i}-th parameters contains parameters not present in the " - f"{i}-th circuit." - ) - - @property - def options(self) -> Options: - """Return the union of estimator options setting and QGT default options, - where, if the same field is set in both, the QGT's default options override - the primitive's default setting. - - Returns: - The QGT default + estimator options. - """ - return self._get_local_options(self._default_options.__dict__) - - def update_default_options(self, **options): - """Update the gradient's default options setting. - - Args: - **options: The fields to update the default options. - """ - - self._default_options.update_options(**options) - - def _get_local_options(self, options: Options) -> Options: - """Return the union of the primitive's default setting, - the QGT default options, and the options in the ``run`` method. - The order of priority is: options in ``run`` method > QGT's default options > primitive's - default setting. - - Args: - options: The fields to update the options - - Returns: - The QGT default + estimator + run options. - """ - opts = copy(self._estimator.options) - opts.update_options(**options) - return opts diff --git a/qiskit/algorithms/gradients/base/base_sampler_gradient.py b/qiskit/algorithms/gradients/base/base_sampler_gradient.py deleted file mode 100644 index b4947365fd5d..000000000000 --- a/qiskit/algorithms/gradients/base/base_sampler_gradient.py +++ /dev/null @@ -1,296 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023 -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Abstract base class of gradient for ``Sampler``. -""" - -from __future__ import annotations - -from abc import ABC, abstractmethod -from collections import defaultdict -from collections.abc import Sequence -from copy import copy - -from qiskit.circuit import Parameter, ParameterExpression, QuantumCircuit -from qiskit.primitives import BaseSampler -from qiskit.primitives.utils import _circuit_key -from qiskit.providers import Options -from qiskit.transpiler.passes import TranslateParameterizedGates - -from .sampler_gradient_result import SamplerGradientResult -from ..utils import ( - GradientCircuit, - _assign_unique_parameters, - _make_gradient_parameters, - _make_gradient_parameter_values, -) - -from ...algorithm_job import AlgorithmJob - - -class BaseSamplerGradient(ABC): - """Base class for a ``SamplerGradient`` to compute the gradients of the sampling probability.""" - - def __init__(self, sampler: BaseSampler, options: Options | None = None): - """ - Args: - sampler: The sampler used to compute the gradients. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting - """ - self._sampler: BaseSampler = sampler - self._default_options = Options() - if options is not None: - self._default_options.update_options(**options) - self._gradient_circuit_cache: dict[tuple, GradientCircuit] = {} - - def run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter] | None] | None = None, - **options, - ) -> AlgorithmJob: - """Run the job of the sampler gradient on the given circuits. - - Args: - circuits: The list of quantum circuits to compute the gradients. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the gradients of - the specified parameters. Each sequence of parameters corresponds to a circuit in - ``circuits``. Defaults to None, which means that the gradients of all parameters in - each circuit are calculated. None in the sequence means that the gradients of all - parameters in the corresponding circuit are calculated. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting - Returns: - The job object of the gradients of the sampling probability. The i-th result - corresponds to ``circuits[i]`` evaluated with parameters bound as ``parameter_values[i]``. - The j-th quasi-probability distribution in the i-th result corresponds to the gradients of - the sampling probability for the j-th parameter in ``circuits[i]``. - - Raises: - ValueError: Invalid arguments are given. - """ - if isinstance(circuits, QuantumCircuit): - # Allow a single circuit to be passed in. - circuits = (circuits,) - if parameters is None: - # If parameters is None, we calculate the gradients of all parameters in each circuit. - parameters = [circuit.parameters for circuit in circuits] - else: - # If parameters is not None, we calculate the gradients of the specified parameters. - # None in parameters means that the gradients of all parameters in the corresponding - # circuit are calculated. - parameters = [ - params if params is not None else circuits[i].parameters - for i, params in enumerate(parameters) - ] - # Validate the arguments. - self._validate_arguments(circuits, parameter_values, parameters) - # The priority of run option is as follows: - # options in `run` method > gradient's default options > primitive's default options. - opts = copy(self._default_options) - opts.update_options(**options) - job = AlgorithmJob(self._run, circuits, parameter_values, parameters, **opts.__dict__) - job.submit() - return job - - @abstractmethod - def _run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> SamplerGradientResult: - """Compute the sampler gradients on the given circuits.""" - raise NotImplementedError() - - def _preprocess( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - supported_gates: Sequence[str], - ) -> tuple[Sequence[QuantumCircuit], Sequence[Sequence[float]], Sequence[set[Parameter]]]: - """Preprocess the gradient. This makes a gradient circuit for each circuit. The gradient - circuit is a transpiled circuit by using the supported gates, and has unique parameters. - ``parameter_values`` and ``parameters`` are also updated to match the gradient circuit. - - Args: - circuits: The list of quantum circuits to compute the gradients. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the gradients of the specified - parameters. - supported_gates: The supported gates used to transpile the circuit. - - Returns: - The list of gradient circuits, the list of parameter values, and the list of parameters. - parameter_values and parameters are updated to match the gradient circuit. - """ - translator = TranslateParameterizedGates(supported_gates) - g_circuits, g_parameter_values, g_parameters = [], [], [] - for circuit, parameter_value_, parameters_ in zip(circuits, parameter_values, parameters): - circuit_key = _circuit_key(circuit) - if circuit_key not in self._gradient_circuit_cache: - unrolled = translator(circuit) - self._gradient_circuit_cache[circuit_key] = _assign_unique_parameters(unrolled) - gradient_circuit = self._gradient_circuit_cache[circuit_key] - g_circuits.append(gradient_circuit.gradient_circuit) - g_parameter_values.append( - _make_gradient_parameter_values(circuit, gradient_circuit, parameter_value_) - ) - g_parameters.append(_make_gradient_parameters(gradient_circuit, parameters_)) - return g_circuits, g_parameter_values, g_parameters - - def _postprocess( - self, - results: SamplerGradientResult, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter] | None], - ) -> SamplerGradientResult: - """Postprocess the gradient. This computes the gradient of the original circuit from the - gradient of the gradient circuit by using the chain rule. - - Args: - results: The results of the gradient of the gradient circuits. - circuits: The list of quantum circuits to compute the gradients. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the gradients of the specified - parameters. - - Returns: - The results of the gradient of the original circuits. - """ - gradients, metadata = [], [] - for idx, (circuit, parameter_values_, parameters_) in enumerate( - zip(circuits, parameter_values, parameters) - ): - gradient_circuit = self._gradient_circuit_cache[_circuit_key(circuit)] - g_parameters = _make_gradient_parameters(gradient_circuit, parameters_) - # Make a map from the gradient parameter to the respective index in the gradient. - g_parameter_indices = {param: i for i, param in enumerate(g_parameters)} - # Compute the original gradient from the gradient of the gradient circuit - # by using the chain rule. - gradient = [] - for parameter in parameters_: - grad_dist: dict[int, float] = defaultdict(float) - for g_parameter, coeff in gradient_circuit.parameter_map[parameter]: - # Compute the coefficient - if isinstance(coeff, ParameterExpression): - local_map = { - p: parameter_values_[circuit.parameters.data.index(p)] - for p in coeff.parameters - } - bound_coeff = coeff.bind(local_map) - else: - bound_coeff = coeff - # The original gradient is a sum of the gradients of the parameters in the - # gradient circuit multiplied by the coefficients. - unique_gradient = results.gradients[idx][g_parameter_indices[g_parameter]] - for key, value in unique_gradient.items(): - grad_dist[key] += float(bound_coeff) * value - gradient.append(dict(grad_dist)) - gradients.append(gradient) - metadata.append([{"parameters": parameters_}]) - return SamplerGradientResult( - gradients=gradients, metadata=metadata, options=results.options - ) - - @staticmethod - def _validate_arguments( - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - ) -> None: - """Validate the arguments of the ``run`` method. - - Args: - circuits: The list of quantum circuits to compute the gradients. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the gradients of the specified - parameters. - - Raises: - ValueError: Invalid arguments are given. - """ - if len(circuits) != len(parameter_values): - raise ValueError( - f"The number of circuits ({len(circuits)}) does not match " - f"the number of parameter value sets ({len(parameter_values)})." - ) - - for i, (circuit, parameter_value) in enumerate(zip(circuits, parameter_values)): - if not circuit.num_parameters: - raise ValueError(f"The {i}-th circuit is not parameterised.") - - if len(parameter_value) != circuit.num_parameters: - raise ValueError( - f"The number of values ({len(parameter_value)}) does not match " - f"the number of parameters ({circuit.num_parameters}) for the {i}-th circuit." - ) - - if len(circuits) != len(parameters): - raise ValueError( - f"The number of circuits ({len(circuits)}) does not match " - f"the number of the specified parameter sets ({len(parameters)})." - ) - - for i, (circuit, parameters_) in enumerate(zip(circuits, parameters)): - if not set(parameters_).issubset(circuit.parameters): - raise ValueError( - f"The {i}-th parameter set contains parameters not present in the " - f"{i}-th circuit." - ) - - @property - def options(self) -> Options: - """Return the union of sampler options setting and gradient default options, - where, if the same field is set in both, the gradient's default options override - the primitive's default setting. - - Returns: - The gradient default + sampler options. - """ - return self._get_local_options(self._default_options.__dict__) - - def update_default_options(self, **options): - """Update the gradient's default options setting. - - Args: - **options: The fields to update the default options. - """ - - self._default_options.update_options(**options) - - def _get_local_options(self, options: Options) -> Options: - """Return the union of the primitive's default setting, - the gradient default options, and the options in the ``run`` method. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - - Args: - options: The fields to update the options - - Returns: - The gradient default + sampler + run options. - """ - opts = copy(self._sampler.options) - opts.update_options(**options) - return opts diff --git a/qiskit/algorithms/gradients/base/estimator_gradient_result.py b/qiskit/algorithms/gradients/base/estimator_gradient_result.py deleted file mode 100644 index ada3bdb2b7bf..000000000000 --- a/qiskit/algorithms/gradients/base/estimator_gradient_result.py +++ /dev/null @@ -1,35 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -Estimator result class -""" - -from __future__ import annotations - -from dataclasses import dataclass -from typing import Any - -import numpy as np - -from qiskit.providers import Options - - -@dataclass(frozen=True) -class EstimatorGradientResult: - """Result of EstimatorGradient.""" - - gradients: list[np.ndarray] - """The gradients of the expectation values.""" - metadata: list[dict[str, Any]] - """Additional information about the job.""" - options: Options - """Primitive runtime options for the execution of the job.""" diff --git a/qiskit/algorithms/gradients/base/qgt_result.py b/qiskit/algorithms/gradients/base/qgt_result.py deleted file mode 100644 index f110e1c68381..000000000000 --- a/qiskit/algorithms/gradients/base/qgt_result.py +++ /dev/null @@ -1,39 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -QGT result class -""" - -from __future__ import annotations - -from dataclasses import dataclass -from typing import Any - -import numpy as np - -from qiskit.providers import Options - -from ..utils import DerivativeType - - -@dataclass(frozen=True) -class QGTResult: - """Result of QGT.""" - - qgts: list[np.ndarray] - """The QGT.""" - derivative_type: DerivativeType - """The type of derivative.""" - metadata: list[dict[str, Any]] - """Additional information about the job.""" - options: Options - """Primitive runtime options for the execution of the job.""" diff --git a/qiskit/algorithms/gradients/base/sampler_gradient_result.py b/qiskit/algorithms/gradients/base/sampler_gradient_result.py deleted file mode 100644 index b78b468f1b9f..000000000000 --- a/qiskit/algorithms/gradients/base/sampler_gradient_result.py +++ /dev/null @@ -1,33 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -Sampler result class -""" - -from __future__ import annotations - -from typing import Any -from dataclasses import dataclass - -from qiskit.providers import Options - - -@dataclass(frozen=True) -class SamplerGradientResult: - """Result of SamplerGradient.""" - - gradients: list[list[dict[int, float]]] - """The gradients of the sample probabilities.""" - metadata: list[dict[str, Any]] - """Additional information about the job.""" - options: Options - """Primitive runtime options for the execution of the job.""" diff --git a/qiskit/algorithms/gradients/finite_diff/__init__.py b/qiskit/algorithms/gradients/finite_diff/__init__.py deleted file mode 100644 index 8f5fd2a37f84..000000000000 --- a/qiskit/algorithms/gradients/finite_diff/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023 -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. diff --git a/qiskit/algorithms/gradients/finite_diff/finite_diff_estimator_gradient.py b/qiskit/algorithms/gradients/finite_diff/finite_diff_estimator_gradient.py deleted file mode 100644 index ea1b987c2ff2..000000000000 --- a/qiskit/algorithms/gradients/finite_diff/finite_diff_estimator_gradient.py +++ /dev/null @@ -1,148 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Gradient of Sampler with Finite difference method.""" - -from __future__ import annotations - -from collections.abc import Sequence -from typing import Literal - -import numpy as np - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseEstimator -from qiskit.providers import Options -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..base.base_estimator_gradient import BaseEstimatorGradient -from ..base.estimator_gradient_result import EstimatorGradientResult - -from ...exceptions import AlgorithmError - - -class FiniteDiffEstimatorGradient(BaseEstimatorGradient): - """ - Compute the gradients of the expectation values by finite difference method [1]. - - **Reference:** - [1] `Finite difference method `_ - """ - - def __init__( - self, - estimator: BaseEstimator, - epsilon: float, - options: Options | None = None, - *, - method: Literal["central", "forward", "backward"] = "central", - ): - r""" - Args: - estimator: The estimator used to compute the gradients. - epsilon: The offset size for the finite difference gradients. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting - method: The computation method of the gradients. - - - ``central`` computes :math:`\frac{f(x+e)-f(x-e)}{2e}`, - - ``forward`` computes :math:`\frac{f(x+e) - f(x)}{e}`, - - ``backward`` computes :math:`\frac{f(x)-f(x-e)}{e}` - - where :math:`e` is epsilon. - - Raises: - ValueError: If ``epsilon`` is not positive. - TypeError: If ``method`` is invalid. - """ - if epsilon <= 0: - raise ValueError(f"epsilon ({epsilon}) should be positive.") - self._epsilon = epsilon - if method not in ("central", "forward", "backward"): - raise TypeError( - f"The argument method should be central, forward, or backward: {method} is given." - ) - self._method = method - super().__init__(estimator, options) - - def _run( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> EstimatorGradientResult: - """Compute the estimator gradients on the given circuits.""" - job_circuits, job_observables, job_param_values, metadata = [], [], [], [] - all_n = [] - - for circuit, observable, parameter_values_, parameters_ in zip( - circuits, observables, parameter_values, parameters - ): - # Indices of parameters to be differentiated - indices = [circuit.parameters.data.index(p) for p in parameters_] - metadata.append({"parameters": parameters_}) - - # Combine inputs into a single job to reduce overhead. - offset = np.identity(circuit.num_parameters)[indices, :] - if self._method == "central": - plus = parameter_values_ + self._epsilon * offset - minus = parameter_values_ - self._epsilon * offset - n = 2 * len(indices) - job_circuits.extend([circuit] * n) - job_observables.extend([observable] * n) - job_param_values.extend(plus.tolist() + minus.tolist()) - all_n.append(n) - elif self._method == "forward": - plus = parameter_values_ + self._epsilon * offset - n = len(indices) + 1 - job_circuits.extend([circuit] * n) - job_observables.extend([observable] * n) - job_param_values.extend([parameter_values_] + plus.tolist()) - all_n.append(n) - elif self._method == "backward": - minus = parameter_values_ - self._epsilon * offset - n = len(indices) + 1 - job_circuits.extend([circuit] * n) - job_observables.extend([observable] * n) - job_param_values.extend([parameter_values_] + minus.tolist()) - all_n.append(n) - - # Run the single job with all circuits. - job = self._estimator.run(job_circuits, job_observables, job_param_values, **options) - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Estimator job failed.") from exc - - # Compute the gradients - gradients = [] - partial_sum_n = 0 - for n in all_n: - if self._method == "central": - result = results.values[partial_sum_n : partial_sum_n + n] - gradient = (result[: n // 2] - result[n // 2 :]) / (2 * self._epsilon) - elif self._method == "forward": - result = results.values[partial_sum_n : partial_sum_n + n] - gradient = (result[1:] - result[0]) / self._epsilon - elif self._method == "backward": - result = results.values[partial_sum_n : partial_sum_n + n] - gradient = (result[0] - result[1:]) / self._epsilon - partial_sum_n += n - gradients.append(gradient) - - opt = self._get_local_options(options) - return EstimatorGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit/algorithms/gradients/finite_diff/finite_diff_sampler_gradient.py b/qiskit/algorithms/gradients/finite_diff/finite_diff_sampler_gradient.py deleted file mode 100644 index bc250286c828..000000000000 --- a/qiskit/algorithms/gradients/finite_diff/finite_diff_sampler_gradient.py +++ /dev/null @@ -1,161 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Gradient of Sampler with Finite difference method.""" - -from __future__ import annotations - -from collections import defaultdict -from typing import Literal, Sequence - -import numpy as np - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.primitives import BaseSampler -from qiskit.providers import Options - -from ..base.base_sampler_gradient import BaseSamplerGradient -from ..base.sampler_gradient_result import SamplerGradientResult - -from ...exceptions import AlgorithmError - - -class FiniteDiffSamplerGradient(BaseSamplerGradient): - """ - Compute the gradients of the sampling probability by finite difference method [1]. - - **Reference:** - [1] `Finite difference method `_ - """ - - def __init__( - self, - sampler: BaseSampler, - epsilon: float, - options: Options | None = None, - *, - method: Literal["central", "forward", "backward"] = "central", - ): - r""" - Args: - sampler: The sampler used to compute the gradients. - epsilon: The offset size for the finite difference gradients. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting - method: The computation method of the gradients. - - - ``central`` computes :math:`\frac{f(x+e)-f(x-e)}{2e}`, - - ``forward`` computes :math:`\frac{f(x+e) - f(x)}{e}`, - - ``backward`` computes :math:`\frac{f(x)-f(x-e)}{e}` - - where :math:`e` is epsilon. - - Raises: - ValueError: If ``epsilon`` is not positive. - TypeError: If ``method`` is invalid. - """ - if epsilon <= 0: - raise ValueError(f"epsilon ({epsilon}) should be positive.") - self._epsilon = epsilon - if method not in ("central", "forward", "backward"): - raise TypeError( - f"The argument method should be central, forward, or backward: {method} is given." - ) - self._method = method - super().__init__(sampler, options) - - def _run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> SamplerGradientResult: - """Compute the sampler gradients on the given circuits.""" - job_circuits, job_param_values, metadata = [], [], [] - all_n = [] - for circuit, parameter_values_, parameters_ in zip(circuits, parameter_values, parameters): - # Indices of parameters to be differentiated - indices = [circuit.parameters.data.index(p) for p in parameters_] - metadata.append({"parameters": parameters_}) - # Combine inputs into a single job to reduce overhead. - offset = np.identity(circuit.num_parameters)[indices, :] - if self._method == "central": - plus = parameter_values_ + self._epsilon * offset - minus = parameter_values_ - self._epsilon * offset - n = 2 * len(indices) - job_circuits.extend([circuit] * n) - job_param_values.extend(plus.tolist() + minus.tolist()) - all_n.append(n) - elif self._method == "forward": - plus = parameter_values_ + self._epsilon * offset - n = len(indices) + 1 - job_circuits.extend([circuit] * n) - job_param_values.extend([parameter_values_] + plus.tolist()) - all_n.append(n) - elif self._method == "backward": - minus = parameter_values_ - self._epsilon * offset - n = len(indices) + 1 - job_circuits.extend([circuit] * n) - job_param_values.extend([parameter_values_] + minus.tolist()) - all_n.append(n) - - # Run the single job with all circuits. - job = self._sampler.run(job_circuits, job_param_values, **options) - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Sampler job failed.") from exc - - # Compute the gradients. - gradients = [] - partial_sum_n = 0 - for n in all_n: - gradient = [] - if self._method == "central": - result = results.quasi_dists[partial_sum_n : partial_sum_n + n] - for dist_plus, dist_minus in zip(result[: n // 2], result[n // 2 :]): - grad_dist: dict[int, float] = defaultdict(float) - for key, value in dist_plus.items(): - grad_dist[key] += value / (2 * self._epsilon) - for key, value in dist_minus.items(): - grad_dist[key] -= value / (2 * self._epsilon) - gradient.append(dict(grad_dist)) - elif self._method == "forward": - result = results.quasi_dists[partial_sum_n : partial_sum_n + n] - dist_zero = result[0] - for dist_plus in result[1:]: - grad_dist = defaultdict(float) - for key, value in dist_plus.items(): - grad_dist[key] += value / self._epsilon - for key, value in dist_zero.items(): - grad_dist[key] -= value / self._epsilon - gradient.append(dict(grad_dist)) - - elif self._method == "backward": - result = results.quasi_dists[partial_sum_n : partial_sum_n + n] - dist_zero = result[0] - for dist_minus in result[1:]: - grad_dist = defaultdict(float) - for key, value in dist_zero.items(): - grad_dist[key] += value / self._epsilon - for key, value in dist_minus.items(): - grad_dist[key] -= value / self._epsilon - gradient.append(dict(grad_dist)) - - partial_sum_n += n - gradients.append(gradient) - - opt = self._get_local_options(options) - return SamplerGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit/algorithms/gradients/lin_comb/__init__.py b/qiskit/algorithms/gradients/lin_comb/__init__.py deleted file mode 100644 index 8f5fd2a37f84..000000000000 --- a/qiskit/algorithms/gradients/lin_comb/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023 -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. diff --git a/qiskit/algorithms/gradients/lin_comb/lin_comb_estimator_gradient.py b/qiskit/algorithms/gradients/lin_comb/lin_comb_estimator_gradient.py deleted file mode 100644 index 93deb48b5820..000000000000 --- a/qiskit/algorithms/gradients/lin_comb/lin_comb_estimator_gradient.py +++ /dev/null @@ -1,195 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -Gradient of probabilities with linear combination of unitaries (LCU) -""" - -from __future__ import annotations - -from collections.abc import Sequence - -import numpy as np - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseEstimator -from qiskit.primitives.utils import init_observable, _circuit_key -from qiskit.providers import Options -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..base.base_estimator_gradient import BaseEstimatorGradient -from ..base.estimator_gradient_result import EstimatorGradientResult -from ..utils import DerivativeType, _make_lin_comb_gradient_circuit, _make_lin_comb_observables - -from ...exceptions import AlgorithmError - - -class LinCombEstimatorGradient(BaseEstimatorGradient): - """Compute the gradients of the expectation values. - This method employs a linear combination of unitaries [1]. - - **Reference:** - [1] Schuld et al., Evaluating analytic gradients on quantum hardware, 2018 - `arXiv:1811.11184 `_ - """ - - SUPPORTED_GATES = [ - "rx", - "ry", - "rz", - "rzx", - "rzz", - "ryy", - "rxx", - "cx", - "cy", - "cz", - "ccx", - "swap", - "iswap", - "h", - "t", - "s", - "sdg", - "x", - "y", - "z", - ] - - def __init__( - self, - estimator: BaseEstimator, - derivative_type: DerivativeType = DerivativeType.REAL, - options: Options | None = None, - ): - r""" - Args: - estimator: The estimator used to compute the gradients. - derivative_type: The type of derivative. Can be either ``DerivativeType.REAL`` - ``DerivativeType.IMAG``, or ``DerivativeType.COMPLEX``. Defaults to - ``DerivativeType.REAL``. - - - ``DerivativeType.REAL`` computes :math:`2 \mathrm{Re}[⟨ψ(ω)|O(θ)|dω ψ(ω)〉]`. - - ``DerivativeType.IMAG`` computes :math:`2 \mathrm{Im}[⟨ψ(ω)|O(θ)|dω ψ(ω)〉]`. - - ``DerivativeType.COMPLEX`` computes :math:`2 ⟨ψ(ω)|O(θ)|dω ψ(ω)〉`. - - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting. - """ - self._lin_comb_cache: dict[tuple, dict[Parameter, QuantumCircuit]] = {} - super().__init__(estimator, options, derivative_type=derivative_type) - - @BaseEstimatorGradient.derivative_type.setter - def derivative_type(self, derivative_type: DerivativeType) -> None: - """Set the derivative type.""" - self._derivative_type = derivative_type - - def _run( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> EstimatorGradientResult: - """Compute the estimator gradients on the given circuits.""" - g_circuits, g_parameter_values, g_parameters = self._preprocess( - circuits, parameter_values, parameters, self.SUPPORTED_GATES - ) - results = self._run_unique( - g_circuits, observables, g_parameter_values, g_parameters, **options - ) - return self._postprocess(results, circuits, parameter_values, parameters) - - def _run_unique( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> EstimatorGradientResult: - """Compute the estimator gradients on the given circuits.""" - job_circuits, job_observables, job_param_values, metadata = [], [], [], [] - all_n = [] - for circuit, observable, parameter_values_, parameters_ in zip( - circuits, observables, parameter_values, parameters - ): - # Prepare circuits for the gradient of the specified parameters. - meta = {"parameters": parameters_} - circuit_key = _circuit_key(circuit) - if circuit_key not in self._lin_comb_cache: - # Cache the circuits for the linear combination of unitaries. - # We only cache the circuits for the specified parameters in the future. - self._lin_comb_cache[circuit_key] = _make_lin_comb_gradient_circuit( - circuit, add_measurement=False - ) - lin_comb_circuits = self._lin_comb_cache[circuit_key] - gradient_circuits = [] - for param in parameters_: - gradient_circuits.append(lin_comb_circuits[param]) - n = len(gradient_circuits) - # Make the observable as :class:`~qiskit.quantum_info.SparsePauliOp` and - # add an ancillary operator to compute the gradient. - observable = init_observable(observable) - observable_1, observable_2 = _make_lin_comb_observables( - observable, self._derivative_type - ) - # If its derivative type is `DerivativeType.COMPLEX`, calculate the gradient - # of the real and imaginary parts separately. - meta["derivative_type"] = self.derivative_type - metadata.append(meta) - # Combine inputs into a single job to reduce overhead. - if self._derivative_type == DerivativeType.COMPLEX: - job_circuits.extend(gradient_circuits * 2) - job_observables.extend([observable_1] * n + [observable_2] * n) - job_param_values.extend([parameter_values_] * 2 * n) - all_n.append(2 * n) - else: - job_circuits.extend(gradient_circuits) - job_observables.extend([observable_1] * n) - job_param_values.extend([parameter_values_] * n) - all_n.append(n) - - # Run the single job with all circuits. - job = self._estimator.run( - job_circuits, - job_observables, - job_param_values, - **options, - ) - try: - results = job.result() - except AlgorithmError as exc: - raise AlgorithmError("Estimator job failed.") from exc - - # Compute the gradients. - gradients = [] - partial_sum_n = 0 - for n in all_n: - # this disable is needed as Pylint does not understand derivative_type is a property if - # it is only defined in the base class and the getter is in the child - # pylint: disable=comparison-with-callable - if self.derivative_type == DerivativeType.COMPLEX: - gradient = np.zeros(n // 2, dtype="complex") - gradient.real = results.values[partial_sum_n : partial_sum_n + n // 2] - gradient.imag = results.values[partial_sum_n + n // 2 : partial_sum_n + n] - - else: - gradient = np.real(results.values[partial_sum_n : partial_sum_n + n]) - partial_sum_n += n - gradients.append(gradient) - - opt = self._get_local_options(options) - return EstimatorGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit/algorithms/gradients/lin_comb/lin_comb_qgt.py b/qiskit/algorithms/gradients/lin_comb/lin_comb_qgt.py deleted file mode 100644 index 0a9e05a9344a..000000000000 --- a/qiskit/algorithms/gradients/lin_comb/lin_comb_qgt.py +++ /dev/null @@ -1,257 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -A class for the Linear Combination Quantum Gradient Tensor. -""" - -from __future__ import annotations - -from collections.abc import Sequence - -import numpy as np - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.primitives import BaseEstimator -from qiskit.primitives.utils import _circuit_key -from qiskit.providers import Options -from qiskit.quantum_info import SparsePauliOp - -from ..base.base_qgt import BaseQGT -from .lin_comb_estimator_gradient import LinCombEstimatorGradient -from ..base.qgt_result import QGTResult -from ..utils import DerivativeType, _make_lin_comb_qgt_circuit, _make_lin_comb_observables - -from ...exceptions import AlgorithmError - - -class LinCombQGT(BaseQGT): - """Computes the Quantum Geometric Tensor (QGT) given a pure, parameterized quantum state. - - This method employs a linear combination of unitaries [1]. - - **Reference:** - - [1]: Schuld et al., "Evaluating analytic gradients on quantum hardware" (2018). - `arXiv:1811.11184 `_ - """ - - SUPPORTED_GATES = [ - "rx", - "ry", - "rz", - "rzx", - "rzz", - "ryy", - "rxx", - "cx", - "cy", - "cz", - "ccx", - "swap", - "iswap", - "h", - "t", - "s", - "sdg", - "x", - "y", - "z", - ] - - def __init__( - self, - estimator: BaseEstimator, - phase_fix: bool = True, - derivative_type: DerivativeType = DerivativeType.COMPLEX, - options: Options | None = None, - ): - r""" - Args: - estimator: The estimator used to compute the QGT. - phase_fix: Whether to calculate the second term (phase fix) of the QGT, which is - :math:`\langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle`. - Default to ``True``. - derivative_type: The type of derivative. Can be either ``DerivativeType.REAL`` - ``DerivativeType.IMAG``, or ``DerivativeType.COMPLEX``. Defaults to - ``DerivativeType.REAL``. - - - ``DerivativeType.REAL`` computes - - .. math:: - - \mathrm{Re(QGT)}_{ij}= \mathrm{Re}[\langle \partial_i \psi | \partial_j \psi \rangle - - \langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle]. - - - ``DerivativeType.IMAG`` computes - - .. math:: - - \mathrm{Re(QGT)}_{ij}= \mathrm{Im}[\langle \partial_i \psi | \partial_j \psi \rangle - - \langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle]. - - - ``DerivativeType.COMPLEX`` computes - - .. math:: - - \mathrm{QGT}_{ij}= [\langle \partial_i \psi | \partial_j \psi \rangle - - \langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle]. - - options: Backend runtime options used for circuit execution. The order of priority is: - options in ``run`` method > QGT's default options > primitive's default - setting. Higher priority setting overrides lower priority setting. - """ - super().__init__(estimator, phase_fix, derivative_type, options=options) - self._gradient = LinCombEstimatorGradient( - estimator, derivative_type=DerivativeType.COMPLEX, options=options - ) - self._lin_comb_qgt_circuit_cache: dict[ - tuple, dict[tuple[Parameter, Parameter], QuantumCircuit] - ] = {} - - def _run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> QGTResult: - """Compute the QGT on the given circuits.""" - g_circuits, g_parameter_values, g_parameters = self._preprocess( - circuits, parameter_values, parameters, self.SUPPORTED_GATES - ) - results = self._run_unique(g_circuits, g_parameter_values, g_parameters, **options) - return self._postprocess(results, circuits, parameter_values, parameters) - - def _run_unique( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> QGTResult: - """Compute the QGTs on the given circuits.""" - job_circuits, job_observables, job_param_values, metadata = [], [], [], [] - all_n, all_m, phase_fixes = [], [], [] - - for circuit, parameter_values_, parameters_ in zip(circuits, parameter_values, parameters): - # Prepare circuits for the gradient of the specified parameters. - parameters_ = [p for p in circuit.parameters if p in parameters_] - meta = {"parameters": parameters_} - metadata.append(meta) - - # Compute the first term in the QGT - circuit_key = _circuit_key(circuit) - if circuit_key not in self._lin_comb_qgt_circuit_cache: - # generate the all of the circuits for the first term in the QGT and cache them. - # Only the circuit related to specified parameters will be executed. - # In the future, we can generate the specified circuits on demand. - self._lin_comb_qgt_circuit_cache[circuit_key] = _make_lin_comb_qgt_circuit(circuit) - lin_comb_qgt_circuits = self._lin_comb_qgt_circuit_cache[circuit_key] - - qgt_circuits = [] - rows, cols = np.triu_indices(len(parameters_)) - for row, col in zip(rows, cols): - param_i = parameters_[row] - param_j = parameters_[col] - qgt_circuits.append(lin_comb_qgt_circuits[(param_i, param_j)]) - - observable = SparsePauliOp.from_list([("I" * circuit.num_qubits, 1)]) - observable_1, observable_2 = _make_lin_comb_observables( - observable, self._derivative_type - ) - - n = len(qgt_circuits) - if self._derivative_type == DerivativeType.COMPLEX: - job_circuits.extend(qgt_circuits * 2) - job_observables.extend([observable_1] * n + [observable_2] * n) - job_param_values.extend([parameter_values_] * 2 * n) - all_m.append(len(parameters_)) - all_n.append(2 * n) - else: - job_circuits.extend(qgt_circuits) - job_observables.extend([observable_1] * n) - job_param_values.extend([parameter_values_] * n) - all_m.append(len(parameters_)) - all_n.append(n) - - # Run the single job with all circuits. - job = self._estimator.run( - job_circuits, - job_observables, - job_param_values, - **options, - ) - - if self._phase_fix: - # Compute the second term in the QGT if phase fix is enabled. - phase_fix_obs = [ - SparsePauliOp.from_list([("I" * circuit.num_qubits, 1)]) for circuit in circuits - ] - phase_fix_job = self._gradient.run( - circuits=circuits, - observables=phase_fix_obs, - parameter_values=parameter_values, - parameters=parameters, - **options, - ) - - try: - results = job.result() - if self._phase_fix: - gradient_results = phase_fix_job.result() - except AlgorithmError as exc: - raise AlgorithmError("Estimator job or gradient job failed.") from exc - - # Compute the phase fix - if self._phase_fix: - for gradient in gradient_results.gradients: - phase_fix = np.outer(np.conjugate(gradient), gradient) - # Select the real or imaginary part of the phase fix if needed - if self.derivative_type == DerivativeType.REAL: - phase_fix = np.real(phase_fix) - elif self.derivative_type == DerivativeType.IMAG: - phase_fix = np.imag(phase_fix) - phase_fixes.append(phase_fix) - else: - phase_fixes = [0 for i in range(len(circuits))] - # Compute the QGT - qgts = [] - partial_sum_n = 0 - for i, (n, m) in enumerate(zip(all_n, all_m)): - qgt = np.zeros((m, m), dtype="complex") - # Compute the first term in the QGT - if self.derivative_type == DerivativeType.COMPLEX: - qgt[np.triu_indices(m)] = results.values[partial_sum_n : partial_sum_n + n // 2] - qgt[np.triu_indices(m)] += ( - 1j * results.values[partial_sum_n + n // 2 : partial_sum_n + n] - ) - elif self.derivative_type == DerivativeType.REAL: - qgt[np.triu_indices(m)] = results.values[partial_sum_n : partial_sum_n + n] - elif self.derivative_type == DerivativeType.IMAG: - qgt[np.triu_indices(m)] = 1j * results.values[partial_sum_n : partial_sum_n + n] - - # Add the conjugate of the upper triangle to the lower triangle - qgt += np.triu(qgt, k=1).conjugate().T - if self.derivative_type == DerivativeType.REAL: - qgt = np.real(qgt) - elif self.derivative_type == DerivativeType.IMAG: - qgt = np.imag(qgt) - - # Subtract the phase fix from the QGT - qgt = qgt - phase_fixes[i] - partial_sum_n += n - qgts.append(qgt / 4) - - opt = self._get_local_options(options) - return QGTResult( - qgts=qgts, derivative_type=self.derivative_type, metadata=metadata, options=opt - ) diff --git a/qiskit/algorithms/gradients/lin_comb/lin_comb_sampler_gradient.py b/qiskit/algorithms/gradients/lin_comb/lin_comb_sampler_gradient.py deleted file mode 100644 index 759e77d460bb..000000000000 --- a/qiskit/algorithms/gradients/lin_comb/lin_comb_sampler_gradient.py +++ /dev/null @@ -1,147 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -Gradient of probabilities with linear combination of unitaries (LCU) -""" - -from __future__ import annotations - -from collections import defaultdict -from collections.abc import Sequence - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.primitives import BaseSampler -from qiskit.primitives.utils import _circuit_key -from qiskit.providers import Options - -from ..base.base_sampler_gradient import BaseSamplerGradient -from ..base.sampler_gradient_result import SamplerGradientResult -from ..utils import _make_lin_comb_gradient_circuit - -from ...exceptions import AlgorithmError - - -class LinCombSamplerGradient(BaseSamplerGradient): - """Compute the gradients of the sampling probability. - This method employs a linear combination of unitaries [1]. - - **Reference:** - [1] Schuld et al., Evaluating analytic gradients on quantum hardware, 2018 - `arXiv:1811.11184 `_ - """ - - SUPPORTED_GATES = [ - "rx", - "ry", - "rz", - "rzx", - "rzz", - "ryy", - "rxx", - "cx", - "cy", - "cz", - "ccx", - "swap", - "iswap", - "h", - "t", - "s", - "sdg", - "x", - "y", - "z", - ] - - def __init__(self, sampler: BaseSampler, options: Options | None = None): - """ - Args: - sampler: The sampler used to compute the gradients. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting - """ - self._lin_comb_cache: dict[tuple, dict[Parameter, QuantumCircuit]] = {} - super().__init__(sampler, options) - - def _run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> SamplerGradientResult: - """Compute the estimator gradients on the given circuits.""" - g_circuits, g_parameter_values, g_parameters = self._preprocess( - circuits, parameter_values, parameters, self.SUPPORTED_GATES - ) - results = self._run_unique(g_circuits, g_parameter_values, g_parameters, **options) - return self._postprocess(results, circuits, parameter_values, parameters) - - def _run_unique( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> SamplerGradientResult: - """Compute the sampler gradients on the given circuits.""" - job_circuits, job_param_values, metadata = [], [], [] - all_n = [] - for circuit, parameter_values_, parameters_ in zip(circuits, parameter_values, parameters): - # Prepare circuits for the gradient of the specified parameters. - metadata.append({"parameters": parameters_}) - circuit_key = _circuit_key(circuit) - if circuit_key not in self._lin_comb_cache: - # Cache the circuits for the linear combination of unitaries. - # We only cache the circuits for the specified parameters in the future. - self._lin_comb_cache[circuit_key] = _make_lin_comb_gradient_circuit( - circuit, add_measurement=True - ) - lin_comb_circuits = self._lin_comb_cache[circuit_key] - gradient_circuits = [] - for param in parameters_: - gradient_circuits.append(lin_comb_circuits[param]) - # Combine inputs into a single job to reduce overhead. - n = len(gradient_circuits) - job_circuits.extend(gradient_circuits) - job_param_values.extend([parameter_values_] * n) - all_n.append(n) - - # Run the single job with all circuits. - job = self._sampler.run(job_circuits, job_param_values, **options) - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Sampler job failed.") from exc - - # Compute the gradients. - gradients = [] - partial_sum_n = 0 - for i, n in enumerate(all_n): - gradient = [] - result = results.quasi_dists[partial_sum_n : partial_sum_n + n] - m = 2 ** circuits[i].num_qubits - for dist in result: - grad_dist: dict[int, float] = defaultdict(float) - for key, value in dist.items(): - if key < m: - grad_dist[key] += value - else: - grad_dist[key - m] -= value - gradient.append(dict(grad_dist)) - gradients.append(gradient) - partial_sum_n += n - - opt = self._get_local_options(options) - return SamplerGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit/algorithms/gradients/param_shift/__init__.py b/qiskit/algorithms/gradients/param_shift/__init__.py deleted file mode 100644 index 8f5fd2a37f84..000000000000 --- a/qiskit/algorithms/gradients/param_shift/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023 -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. diff --git a/qiskit/algorithms/gradients/param_shift/param_shift_estimator_gradient.py b/qiskit/algorithms/gradients/param_shift/param_shift_estimator_gradient.py deleted file mode 100644 index ef334a291e6e..000000000000 --- a/qiskit/algorithms/gradients/param_shift/param_shift_estimator_gradient.py +++ /dev/null @@ -1,123 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -Gradient of probabilities with parameter shift -""" - -from __future__ import annotations - -from collections.abc import Sequence - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..base.base_estimator_gradient import BaseEstimatorGradient -from ..base.estimator_gradient_result import EstimatorGradientResult -from ..utils import _make_param_shift_parameter_values - -from ...exceptions import AlgorithmError - - -class ParamShiftEstimatorGradient(BaseEstimatorGradient): - """ - Compute the gradients of the expectation values by the parameter shift rule [1]. - - **Reference:** - [1] Schuld, M., Bergholm, V., Gogolin, C., Izaac, J., and Killoran, N. Evaluating analytic - gradients on quantum hardware, `DOI `_ - """ - - SUPPORTED_GATES = [ - "x", - "y", - "z", - "h", - "rx", - "ry", - "rz", - "p", - "cx", - "cy", - "cz", - "ryy", - "rxx", - "rzz", - "rzx", - ] - - def _run( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> EstimatorGradientResult: - """Compute the gradients of the expectation values by the parameter shift rule.""" - g_circuits, g_parameter_values, g_parameters = self._preprocess( - circuits, parameter_values, parameters, self.SUPPORTED_GATES - ) - results = self._run_unique( - g_circuits, observables, g_parameter_values, g_parameters, **options - ) - return self._postprocess(results, circuits, parameter_values, parameters) - - def _run_unique( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> EstimatorGradientResult: - """Compute the estimator gradients on the given circuits.""" - job_circuits, job_observables, job_param_values, metadata = [], [], [], [] - all_n = [] - for circuit, observable, parameter_values_, parameters_ in zip( - circuits, observables, parameter_values, parameters - ): - metadata.append({"parameters": parameters_}) - # Make parameter values for the parameter shift rule. - param_shift_parameter_values = _make_param_shift_parameter_values( - circuit, parameter_values_, parameters_ - ) - # Combine inputs into a single job to reduce overhead. - n = len(param_shift_parameter_values) - job_circuits.extend([circuit] * n) - job_observables.extend([observable] * n) - job_param_values.extend(param_shift_parameter_values) - all_n.append(n) - - # Run the single job with all circuits. - job = self._estimator.run( - job_circuits, - job_observables, - job_param_values, - **options, - ) - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Estimator job failed.") from exc - - # Compute the gradients. - gradients = [] - partial_sum_n = 0 - for n in all_n: - result = results.values[partial_sum_n : partial_sum_n + n] - gradient_ = (result[: n // 2] - result[n // 2 :]) / 2 - gradients.append(gradient_) - partial_sum_n += n - - opt = self._get_local_options(options) - return EstimatorGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit/algorithms/gradients/param_shift/param_shift_sampler_gradient.py b/qiskit/algorithms/gradients/param_shift/param_shift_sampler_gradient.py deleted file mode 100644 index 642f4b002cd9..000000000000 --- a/qiskit/algorithms/gradients/param_shift/param_shift_sampler_gradient.py +++ /dev/null @@ -1,117 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -Gradient of probabilities with parameter shift -""" - -from __future__ import annotations - -from collections import defaultdict -from collections.abc import Sequence - -from qiskit.circuit import Parameter, QuantumCircuit - -from ..base.base_sampler_gradient import BaseSamplerGradient -from ..base.sampler_gradient_result import SamplerGradientResult -from ..utils import _make_param_shift_parameter_values - -from ...exceptions import AlgorithmError - - -class ParamShiftSamplerGradient(BaseSamplerGradient): - """ - Compute the gradients of the sampling probability by the parameter shift rule [1]. - - **Reference:** - [1] Schuld, M., Bergholm, V., Gogolin, C., Izaac, J., and Killoran, N. Evaluating analytic - gradients on quantum hardware, `DOI `_ - """ - - SUPPORTED_GATES = [ - "x", - "y", - "z", - "h", - "rx", - "ry", - "rz", - "p", - "cx", - "cy", - "cz", - "ryy", - "rxx", - "rzz", - "rzx", - ] - - def _run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> SamplerGradientResult: - """Compute the estimator gradients on the given circuits.""" - g_circuits, g_parameter_values, g_parameters = self._preprocess( - circuits, parameter_values, parameters, self.SUPPORTED_GATES - ) - results = self._run_unique(g_circuits, g_parameter_values, g_parameters, **options) - return self._postprocess(results, circuits, parameter_values, parameters) - - def _run_unique( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> SamplerGradientResult: - """Compute the sampler gradients on the given circuits.""" - job_circuits, job_param_values, metadata = [], [], [] - all_n = [] - for circuit, parameter_values_, parameters_ in zip(circuits, parameter_values, parameters): - metadata.append({"parameters": parameters_}) - # Make parameter values for the parameter shift rule. - param_shift_parameter_values = _make_param_shift_parameter_values( - circuit, parameter_values_, parameters_ - ) - # Combine inputs into a single job to reduce overhead. - n = len(param_shift_parameter_values) - job_circuits.extend([circuit] * n) - job_param_values.extend(param_shift_parameter_values) - all_n.append(n) - - # Run the single job with all circuits. - job = self._sampler.run(job_circuits, job_param_values, **options) - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Estimator job failed.") from exc - - # Compute the gradients. - gradients = [] - partial_sum_n = 0 - for n in all_n: - gradient = [] - result = results.quasi_dists[partial_sum_n : partial_sum_n + n] - for dist_plus, dist_minus in zip(result[: n // 2], result[n // 2 :]): - grad_dist: dict[int, float] = defaultdict(float) - for key, val in dist_plus.items(): - grad_dist[key] += val / 2 - for key, val in dist_minus.items(): - grad_dist[key] -= val / 2 - gradient.append(dict(grad_dist)) - gradients.append(gradient) - partial_sum_n += n - - opt = self._get_local_options(options) - return SamplerGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit/algorithms/gradients/qfi.py b/qiskit/algorithms/gradients/qfi.py deleted file mode 100644 index 94aa86fde56a..000000000000 --- a/qiskit/algorithms/gradients/qfi.py +++ /dev/null @@ -1,171 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023 -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -A class for the Quantum Fisher Information. -""" - -from __future__ import annotations - -from abc import ABC -from collections.abc import Sequence -from copy import copy - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.providers import Options - -from .base.base_qgt import BaseQGT -from .lin_comb.lin_comb_estimator_gradient import DerivativeType -from .qfi_result import QFIResult - -from ..algorithm_job import AlgorithmJob -from ..exceptions import AlgorithmError - - -class QFI(ABC): - r"""Computes the Quantum Fisher Information (QFI) given a pure, - parameterized quantum state. QFI is defined as: - - .. math:: - - \mathrm{QFI}_{ij}= 4 \mathrm{Re}[\langle \partial_i \psi | \partial_j \psi \rangle - - \langle\partial_i \psi | \psi \rangle \langle\psi | \partial_j \psi \rangle]. - """ - - def __init__( - self, - qgt: BaseQGT, - options: Options | None = None, - ): - r""" - Args: - qgt: The quantum geometric tensor used to compute the QFI. - options: Backend runtime options used for circuit execution. The order of priority is: - options in ``run`` method > QFI's default options > primitive's default - setting. Higher priority setting overrides lower priority setting. - """ - self._qgt: BaseQGT = qgt - self._default_options = Options() - if options is not None: - self._default_options.update_options(**options) - - def run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter] | None] | None = None, - **options, - ) -> AlgorithmJob: - """Run the job of the QFIs on the given circuits. - - Args: - circuits: The list of quantum circuits to compute the QFIs. - parameter_values: The list of parameter values to be bound to the circuit. - parameters: The sequence of parameters to calculate only the QFIs of - the specified parameters. Each sequence of parameters corresponds to a circuit in - ``circuits``. Defaults to None, which means that the QFIs of all parameters in - each circuit are calculated. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > QFI's - default options > QGT's default setting. - Higher priority setting overrides lower priority setting. - - Returns: - The job object of the QFIs of the expectation values. The i-th result corresponds to - ``circuits[i]`` evaluated with parameters bound as ``parameter_values[i]``. - """ - - if isinstance(circuits, QuantumCircuit): - # Allow a single circuit to be passed in. - circuits = (circuits,) - - if parameters is None: - # If parameters is None, we calculate the gradients of all parameters in each circuit. - parameters = [circuit.parameters for circuit in circuits] - else: - # If parameters is not None, we calculate the gradients of the specified parameters. - # None in parameters means that the gradients of all parameters in the corresponding - # circuit are calculated. - parameters = [ - params if params is not None else circuits[i].parameters - for i, params in enumerate(parameters) - ] - # The priority of run option is as follows: - # options in ``run`` method > QFI's default options > QGT's default setting. - opts = copy(self._default_options) - opts.update_options(**options) - job = AlgorithmJob(self._run, circuits, parameter_values, parameters, **opts.__dict__) - job.submit() - return job - - def _run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> QFIResult: - """Compute the QFI on the given circuits.""" - # Set the derivative type to real - temp_derivative_type, self._qgt.derivative_type = ( - self._qgt.derivative_type, - DerivativeType.REAL, - ) - job = self._qgt.run(circuits, parameter_values, parameters, **options) - - try: - result = job.result() - except AlgorithmError as exc: - raise AlgorithmError("Estimator job or gradient job failed.") from exc - - self._qgt.derivative_type = temp_derivative_type - - return QFIResult( - qfis=[4 * qgt.real for qgt in result.qgts], - metadata=result.metadata, - options=result.options, - ) - - @property - def options(self) -> Options: - """Return the union of QGT's options setting and QFI's default options, - where, if the same field is set in both, the QFI's default options override - the QGT's default setting. - - Returns: - The QFI default + QGT options. - """ - return self._get_local_options(self._default_options.__dict__) - - def update_default_options(self, **options): - """Update the gradient's default options setting. - - Args: - **options: The fields to update the default options. - """ - - self._default_options.update_options(**options) - - def _get_local_options(self, options: Options) -> Options: - """Return the union of the QFI default setting, - the QGT default options, and the options in the ``run`` method. - The order of priority is: options in ``run`` method > QFI's default options > QGT's - default setting. - - Args: - options: The fields to update the options - - Returns: - The QFI default + QGT default + run options. - """ - opts = copy(self._qgt.options) - opts.update_options(**options) - return opts diff --git a/qiskit/algorithms/gradients/qfi_result.py b/qiskit/algorithms/gradients/qfi_result.py deleted file mode 100644 index 47a04021584d..000000000000 --- a/qiskit/algorithms/gradients/qfi_result.py +++ /dev/null @@ -1,35 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -QFI result class -""" - -from __future__ import annotations - -from dataclasses import dataclass -from typing import Any - -import numpy as np - -from qiskit.providers import Options - - -@dataclass(frozen=True) -class QFIResult: - """Result of QFI.""" - - qfis: list[np.ndarray] - """The QFI.""" - metadata: list[dict[str, Any]] - """Additional information about the job.""" - options: Options - """Primitive runtime options for the execution of the job.""" diff --git a/qiskit/algorithms/gradients/reverse/__init__.py b/qiskit/algorithms/gradients/reverse/__init__.py deleted file mode 100644 index fdb172d367f0..000000000000 --- a/qiskit/algorithms/gradients/reverse/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. diff --git a/qiskit/algorithms/gradients/reverse/bind.py b/qiskit/algorithms/gradients/reverse/bind.py deleted file mode 100644 index 7660f7c836d0..000000000000 --- a/qiskit/algorithms/gradients/reverse/bind.py +++ /dev/null @@ -1,53 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Bind values to a parametrized circuit, accepting binds for non-existing parameters in the circuit.""" - -from __future__ import annotations -from collections.abc import Iterable - -from qiskit.circuit import QuantumCircuit, Parameter - -# pylint: disable=inconsistent-return-statements -def bind( - circuits: QuantumCircuit | Iterable[QuantumCircuit], - parameter_binds: dict[Parameter, float], - inplace: bool = False, -) -> QuantumCircuit | Iterable[QuantumCircuit] | None: - """Bind parameters in a circuit (or list of circuits). - - This method also allows passing parameter binds to parameters that are not in the circuit, - and thereby differs to :meth:`.QuantumCircuit.assign_parameters`. - - Args: - circuits: Input circuit(s). - parameter_binds: A dictionary with ``{Parameter: float}`` pairs determining the values to - which the free parameters in the circuit(s) are bound. - inplace: If ``True``, bind the values in place, otherwise return circuit copies. - - Returns: - The bound circuits, if ``inplace=False``, otherwise None. - - """ - if not isinstance(circuits, Iterable): - circuits = [circuits] - return_list = False - else: - return_list = True - - bound = [] - for circuit in circuits: - existing_parameter_binds = {p: parameter_binds[p] for p in circuit.parameters} - bound.append(circuit.assign_parameters(existing_parameter_binds, inplace=inplace)) - - if not inplace: - return bound if return_list else bound[0] diff --git a/qiskit/algorithms/gradients/reverse/derive_circuit.py b/qiskit/algorithms/gradients/reverse/derive_circuit.py deleted file mode 100644 index 96a2bead60cd..000000000000 --- a/qiskit/algorithms/gradients/reverse/derive_circuit.py +++ /dev/null @@ -1,157 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Split a circuit into subcircuits, each containing a single parameterized gate.""" - -from __future__ import annotations -import itertools -from collections.abc import Sequence - -from qiskit.circuit import QuantumCircuit, Parameter, Gate -from qiskit.circuit.library import RXGate, RYGate, RZGate, CRXGate, CRYGate, CRZGate - - -def gradient_lookup(gate: Gate) -> list[tuple[complex, QuantumCircuit]]: - """Returns a circuit implementing the gradient of the input gate. - - Args: - gate: The gate whose derivative is returned. - - Returns: - The derivative of the input gate as list of ``(coeff, circuit)`` pairs, - where the sum of all ``coeff * circuit`` elements describes the full derivative. - The circuit is the unitary part of the derivative with a potential separate ``coeff``. - The output is a list as derivatives of e.g. controlled gates can only be described - as a sum of ``coeff * circuit`` pairs. - - Raises: - NotImplementedError: If the derivative of ``gate`` is not implemented. - """ - - param = gate.params[0] - if isinstance(gate, RXGate): - derivative = QuantumCircuit(gate.num_qubits) - derivative.rx(param, 0) - derivative.x(0) - return [(-0.5j, derivative)] - if isinstance(gate, RYGate): - derivative = QuantumCircuit(gate.num_qubits) - derivative.ry(param, 0) - derivative.y(0) - return [(-0.5j, derivative)] - if isinstance(gate, RZGate): - derivative = QuantumCircuit(gate.num_qubits) - derivative.rz(param, 0) - derivative.z(0) - return [(-0.5j, derivative)] - if isinstance(gate, CRXGate): - proj1 = QuantumCircuit(gate.num_qubits) - proj1.rx(param, 1) - proj1.x(1) - - proj2 = QuantumCircuit(gate.num_qubits) - proj2.z(0) - proj2.rx(param, 1) - proj2.x(1) - - return [(-0.25j, proj1), (0.25j, proj2)] - if isinstance(gate, CRYGate): - proj1 = QuantumCircuit(gate.num_qubits) - proj1.ry(param, 1) - proj1.y(1) - - proj2 = QuantumCircuit(gate.num_qubits) - proj2.z(0) - proj2.ry(param, 1) - proj2.y(1) - - return [(-0.25j, proj1), (0.25j, proj2)] - if isinstance(gate, CRZGate): - proj1 = QuantumCircuit(gate.num_qubits) - proj1.rz(param, 1) - proj1.z(1) - - proj2 = QuantumCircuit(gate.num_qubits) - proj2.z(0) - proj2.rz(param, 1) - proj2.z(1) - - return [(-0.25j, proj1), (0.25j, proj2)] - raise NotImplementedError("Cannot implement gradient for", gate) - - -def derive_circuit( - circuit: QuantumCircuit, parameter: Parameter -) -> Sequence[tuple[complex, QuantumCircuit]]: - """Return the analytic gradient expression of the input circuit wrt. a single parameter. - - Returns a list of ``(coeff, gradient_circuit)`` tuples, where the derivative of the circuit is - given by the sum of the gradient circuits multiplied by their coefficient. - - For example, the circuit:: - - ┌───┐┌───────┐┌─────┐ - q: ┤ H ├┤ Rx(x) ├┤ Sdg ├ - └───┘└───────┘└─────┘ - - returns the coefficient `-0.5j` and the circuit equivalent to:: - - ┌───┐┌───────┐┌───┐┌─────┐ - q: ┤ H ├┤ Rx(x) ├┤ X ├┤ Sdg ├ - └───┘└───────┘└───┘└─────┘ - - as the derivative of `Rx(x)` is `-0.5j Rx(x) X`. - - Args: - circuit: The quantum circuit to derive. - parameter: The parameter with respect to which we derive. - - Returns: - A list of ``(coeff, gradient_circuit)`` tuples. - - Raises: - ValueError: If ``parameter`` is of the wrong type. - ValueError: If ``parameter`` is not in this circuit. - NotImplementedError: If a non-unique parameter is added, as the product rule is not yet - supported in this function. - """ - # this is added as useful user-warning, since sometimes ``ParameterExpression``s are - # passed around instead of ``Parameter``s - if not isinstance(parameter, Parameter): - raise ValueError(f"parameter must be of type Parameter, not {type(parameter)}.") - - if parameter not in circuit.parameters: - raise ValueError(f"The parameter {parameter} is not in this circuit.") - - if len(circuit._parameter_table[parameter]) > 1: - raise NotImplementedError("No product rule support yet, circuit parameters must be unique.") - - summands, op_context = [], [] - for i, op in enumerate(circuit.data): - gate = op.operation - op_context.append((op.qubits, op.clbits)) - if parameter in gate.params: - coeffs_and_grads = gradient_lookup(gate) - summands += [coeffs_and_grads] - else: - summands += [[(1, gate)]] - - gradient = [] - for product_rule_term in itertools.product(*summands): - summand_circuit = QuantumCircuit(*circuit.qregs) - c = 1 - for i, term in enumerate(product_rule_term): - c *= term[0] - summand_circuit.data.append([term[1], *op_context[i]]) - gradient += [(c, summand_circuit.copy())] - - return gradient diff --git a/qiskit/algorithms/gradients/reverse/reverse_gradient.py b/qiskit/algorithms/gradients/reverse/reverse_gradient.py deleted file mode 100644 index c3bf8005d377..000000000000 --- a/qiskit/algorithms/gradients/reverse/reverse_gradient.py +++ /dev/null @@ -1,200 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Estimator gradients with the classically efficient reverse mode.""" - -from __future__ import annotations -from collections.abc import Sequence -import logging - -import numpy as np - -from qiskit.circuit import QuantumCircuit, Parameter -from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.quantum_info import Statevector -from qiskit.opflow import PauliSumOp -from qiskit.primitives import Estimator - -from .bind import bind -from .derive_circuit import derive_circuit -from .split_circuits import split - -from ..base.base_estimator_gradient import BaseEstimatorGradient -from ..base.estimator_gradient_result import EstimatorGradientResult -from ..utils import DerivativeType - -logger = logging.getLogger(__name__) - - -class ReverseEstimatorGradient(BaseEstimatorGradient): - """Estimator gradients with the classically efficient reverse mode. - - .. note:: - - This gradient implementation is based on statevector manipulations and scales - exponentially with the number of qubits. However, for small system sizes it can be very fast - compared to circuit-based gradients. - - This class implements the calculation of the expectation gradient as described in - [1]. By keeping track of two statevectors and iteratively sweeping through each parameterized - gate, this method scales only linearly with the number of parameters. - - **References:** - - [1]: Jones, T. and Gacon, J. "Efficient calculation of gradients in classical simulations - of variational quantum algorithms" (2020). - `arXiv:2009.02823 `_. - - """ - - SUPPORTED_GATES = ["rx", "ry", "rz", "cp", "crx", "cry", "crz"] - - def __init__(self, derivative_type: DerivativeType = DerivativeType.REAL): - """ - Args: - derivative_type: Defines whether the real, imaginary or real plus imaginary part - of the gradient is returned. - """ - dummy_estimator = Estimator() # this is required by the base class, but not used - super().__init__(dummy_estimator, derivative_type=derivative_type) - - @BaseEstimatorGradient.derivative_type.setter - def derivative_type(self, derivative_type: DerivativeType) -> None: - """Set the derivative type.""" - self._derivative_type = derivative_type - - def _run( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> EstimatorGradientResult: - """Compute the gradients of the expectation values by the parameter shift rule.""" - g_circuits, g_parameter_values, g_parameters = self._preprocess( - circuits, parameter_values, parameters, self.SUPPORTED_GATES - ) - results = self._run_unique( - g_circuits, observables, g_parameter_values, g_parameters, **options - ) - return self._postprocess(results, circuits, parameter_values, parameters) - - def _run_unique( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, # pylint: disable=unused-argument - ) -> EstimatorGradientResult: - num_gradients = len(circuits) - gradients = [] - metadata = [] - - for i in range(num_gradients): - # temporary variables for easier access - circuit = circuits[i] - parameters_ = parameters[i] - observable = observables[i] - values = parameter_values[i] - - # the metadata only contains the parameters as there are no run configs here - metadata.append( - { - "parameters": parameters_, - "derivative_type": self.derivative_type, - } - ) - - # keep track of the parameter order of the circuit, as the circuit splitting might - # produce a list of unitaries in a different order - # original_parameter_order = [p for p in circuit.parameters if p in parameters_] - - # split the circuit and generate lists of unitaries [U_1, U_2, ...] and - # parameters [p_1, p_2, ...] in these unitaries - unitaries, paramlist = split(circuit, parameters=parameters_) - - parameter_binds = dict(zip(circuit.parameters, values)) - bound_circuit = bind(circuit, parameter_binds) - - # initialize state variables -- we use the same naming as in the paper - phi = Statevector(bound_circuit) - lam = _evolve_by_operator(observable, phi) - - # store gradients in a dictionary to return them in the correct order - grads = {param: 0j for param in parameters_} - - num_parameters = len(unitaries) - for j in reversed(range(num_parameters)): - unitary_j = unitaries[j] - - # We currently only support gates with a single parameter -- which is reflected - # in self.SUPPORTED_GATES -- but generally we could also support gates with multiple - # parameters per gate - parameter_j = paramlist[j][0] - - # get the analytic gradient d U_j / d p_j and bind the gate - deriv = derive_circuit(unitary_j, parameter_j) - for _, gate in deriv: - bind(gate, parameter_binds, inplace=True) - - # iterate the state variable - unitary_j_dagger = bind(unitary_j, parameter_binds).inverse() - phi = phi.evolve(unitary_j_dagger) - - # compute current gradient - grad = sum( - coeff * lam.conjugate().data.dot(phi.evolve(gate).data) for coeff, gate in deriv - ) - - # Compute the full gradient (real and complex parts) as all information is available. - # Later, based on the derivative type, cast to real/imag/complex. - grads[parameter_j] += grad - - if j > 0: - lam = lam.evolve(unitary_j_dagger) - - gradient = np.array(list(grads.values())) - gradients.append(self._to_derivtype(gradient)) - - result = EstimatorGradientResult(gradients, metadata=metadata, options={}) - return result - - def _to_derivtype(self, gradient): - # this disable is needed as Pylint does not understand derivative_type is a property if - # it is only defined in the base class and the getter is in the child - # pylint: disable=comparison-with-callable - if self.derivative_type == DerivativeType.REAL: - return 2 * np.real(gradient) - if self.derivative_type == DerivativeType.IMAG: - return 2 * np.imag(gradient) - - return 2 * gradient - - -def _evolve_by_operator(operator, state): - """Evolve the Statevector state by operator.""" - - # try casting to sparse matrix and use sparse matrix-vector multiplication, which is - # a lot faster than using Statevector.evolve - if isinstance(operator, PauliSumOp): - operator = operator.primitive * operator.coeff - - try: - spmatrix = operator.to_matrix(sparse=True) - evolved = spmatrix @ state.data - return Statevector(evolved) - except (TypeError, AttributeError): - logger.info("Operator is not castable to a sparse matrix, using Statevector.evolve.") - - return state.evolve(operator) diff --git a/qiskit/algorithms/gradients/reverse/reverse_qgt.py b/qiskit/algorithms/gradients/reverse/reverse_qgt.py deleted file mode 100644 index 0b845ee96961..000000000000 --- a/qiskit/algorithms/gradients/reverse/reverse_qgt.py +++ /dev/null @@ -1,252 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""QGT with the classically efficient reverse mode.""" - -from __future__ import annotations -from collections.abc import Sequence -import logging - -import numpy as np - -from qiskit.circuit import QuantumCircuit, Parameter -from qiskit.quantum_info import Statevector -from qiskit.providers import Options -from qiskit.primitives import Estimator - -from ..base.base_qgt import BaseQGT -from ..base.qgt_result import QGTResult -from ..utils import DerivativeType - -from .split_circuits import split -from .bind import bind -from .derive_circuit import derive_circuit - -logger = logging.getLogger(__name__) - - -class ReverseQGT(BaseQGT): - """QGT calculation with the classically efficient reverse mode. - - .. note:: - - This QGT implementation is based on statevector manipulations and scales exponentially - with the number of qubits. However, for small system sizes it can be very fast - compared to circuit-based gradients. - - This class implements the calculation of the QGT as described in [1]. - By keeping track of three statevectors and iteratively sweeping through each parameterized - gate, this method scales only quadratically with the number of parameters. - - **References:** - - [1]: Jones, T. "Efficient classical calculation of the Quantum Natural Gradient" (2020). - `arXiv:2011.02991 `_. - - """ - - SUPPORTED_GATES = ["rx", "ry", "rz", "cp", "crx", "cry", "crz"] - - def __init__( - self, phase_fix: bool = True, derivative_type: DerivativeType = DerivativeType.COMPLEX - ): - """ - Args: - phase_fix: Whether or not to include the phase fix. - derivative_type: Determines whether the complex QGT or only the real or imaginary - parts are calculated. - """ - dummy_estimator = Estimator() # this method does not need an estimator - super().__init__(dummy_estimator, phase_fix, derivative_type) - - @property - def options(self) -> Options: - """There are no options for the reverse QGT, returns an empty options dict. - - Returns: - Empty options. - """ - return Options() - - def _run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameter_sets: Sequence[set[Parameter]], - **options, - ) -> QGTResult: - """Compute the QGT on the given circuits.""" - g_circuits, g_parameter_values, g_parameter_sets = self._preprocess( - circuits, parameter_values, parameter_sets, self.SUPPORTED_GATES - ) - results = self._run_unique(g_circuits, g_parameter_values, g_parameter_sets, **options) - return self._postprocess(results, circuits, parameter_values, parameter_sets) - - def _run_unique( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameter_sets: Sequence[set[Parameter]], - **options, # pylint: disable=unused-argument - ) -> QGTResult: - num_qgts = len(circuits) - qgts = [] - metadata = [] - - for k in range(num_qgts): - values = np.asarray(parameter_values[k]) - circuit = circuits[k] - parameters = list(parameter_sets[k]) - - num_parameters = len(parameters) - original_parameter_order = [p for p in circuit.parameters if p in parameters] - metadata.append({"parameters": original_parameter_order}) - - unitaries, paramlist = split(circuit, parameters=parameters) - - # initialize the phase fix vector and the hessian part ``metric`` - num_parameters = len(unitaries) - phase_fixes = np.zeros(num_parameters, dtype=complex) - metric = np.zeros((num_parameters, num_parameters), dtype=complex) - - # initialize the state variables -- naming convention is the same as the paper - parameter_binds = dict(zip(circuit.parameters, values)) - bound_unitaries = bind(unitaries, parameter_binds) - - chi = Statevector(bound_unitaries[0]) - psi = chi.copy() - phi = Statevector.from_int(0, (2,) * circuit.num_qubits) - - # Get the analytic gradient of the first unitary - # Note: We currently only support gates with a single parameter -- which is reflected - # in self.SUPPORTED_GATES -- but generally we could also support gates with multiple - # parameters per gate. This is the reason for the second 0-index. - deriv = derive_circuit(unitaries[0], paramlist[0][0]) - for _, gate in deriv: - bind(gate, parameter_binds, inplace=True) - - grad_coeffs = [coeff for coeff, _ in deriv] - grad_states = [phi.evolve(gate) for _, gate in deriv] - - # compute phase fix (optional) and the hessian part - if self._phase_fix: - phase_fixes[0] = _phasefix_term(chi, grad_coeffs, grad_states) - - metric[0, 0] = _l_term(grad_coeffs, grad_states, grad_coeffs, grad_states) - - for j in range(1, num_parameters): - lam = psi.copy() - phi = psi.copy() - - # get the analytic gradient d U_j / d p_j and apply it - deriv = derive_circuit(unitaries[j], paramlist[j][0]) - - for _, gate in deriv: - bind(gate, parameter_binds, inplace=True) - - # compute |phi> (in general it's a sum of states and coeffs) - grad_coeffs = [coeff for coeff, _ in deriv] - grad_states = [phi.evolve(gate) for _, gate in deriv] - - # compute the digaonal element L_{j, j} - metric[j, j] += _l_term(grad_coeffs, grad_states, grad_coeffs, grad_states) - - # compute the off diagonal elements L_{i, j} - for i in reversed(range(j)): - # apply U_{i + 1}_dg - unitary_ip_inv = bound_unitaries[i + 1].inverse() - grad_states = [state.evolve(unitary_ip_inv) for state in grad_states] - - lam = lam.evolve(bound_unitaries[i].inverse()) - - # get the gradient d U_i / d p_i and apply it - deriv = derive_circuit(unitaries[i], paramlist[i][0]) - for _, gate in deriv: - bind(gate, parameter_binds, inplace=True) - - grad_coeffs_mu = [coeff for coeff, _ in deriv] - grad_states_mu = [lam.evolve(gate) for _, gate in deriv] - - metric[i, j] += _l_term( - grad_coeffs_mu, grad_states_mu, grad_coeffs, grad_states - ) - - if self._phase_fix: - phase_fixes[j] += _phasefix_term(chi, grad_coeffs, grad_states) - - psi = psi.evolve(bound_unitaries[j]) - - # The following code stacks the QGT together and maps the values into the - # correct original order of parameters - - # map circuit parameter to global index in the circuit - param_to_circuit = { - param: index for index, param in enumerate(original_parameter_order) - } - # map global index to the local index used in the calculation, the new index can - # now be accessed by remap[index] - remap = { - index: param_to_circuit[_extract_parameter(plist[0])] - for index, plist in enumerate(paramlist) - } - - qgt = np.zeros((num_parameters, num_parameters), dtype=complex) - for i in range(num_parameters): - iloc = remap[i] - for j in range(num_parameters): - jloc = remap[j] - if i <= j: - qgt[iloc, jloc] += metric[i, j] - else: - qgt[iloc, jloc] += np.conj(metric[j, i]) - - qgt[iloc, jloc] -= np.conj(phase_fixes[i]) * phase_fixes[j] - - # append and cast to real/imag if required - qgts.append(self._to_derivtype(qgt)) - - result = QGTResult(qgts, self.derivative_type, metadata, options=None) - return result - - def _to_derivtype(self, qgt): - if self.derivative_type == DerivativeType.REAL: - return np.real(qgt) - if self.derivative_type == DerivativeType.IMAG: - return np.imag(qgt) - - return qgt - - -def _l_term(coeffs_i, states_i, coeffs_j, states_j): - return sum( - sum( - np.conj(coeff_i) * coeff_j * np.conj(state_i.data).dot(state_j.data) - for coeff_i, state_i in zip(coeffs_i, states_i) - ) - for coeff_j, state_j in zip(coeffs_j, states_j) - ) - - -def _phasefix_term(chi, coeffs, states): - return sum( - coeff_i * np.conj(chi.data).dot(state_i.data) for coeff_i, state_i in zip(coeffs, states) - ) - - -def _extract_parameter(expression): - if isinstance(expression, Parameter): - return expression - - if len(expression.parameters) > 1: - raise ValueError("Expression has more than one parameter.") - - return list(expression.parameters)[0] diff --git a/qiskit/algorithms/gradients/reverse/split_circuits.py b/qiskit/algorithms/gradients/reverse/split_circuits.py deleted file mode 100644 index b2bdf2b5375b..000000000000 --- a/qiskit/algorithms/gradients/reverse/split_circuits.py +++ /dev/null @@ -1,68 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Split a circuit into subcircuits, each containing a single parameterized gate.""" - -from __future__ import annotations - -from collections.abc import Iterable -from qiskit.circuit import QuantumCircuit, ParameterExpression, Parameter - - -def split( - circuit: QuantumCircuit, - parameters: Iterable[Parameter] | None = None, -) -> tuple[list[QuantumCircuit], list[list[Parameter]]]: - """Split the circuit at ParameterExpressions. - - Args: - circuit: The circuit to split. - parameters: The parameters at which to split. If None, split at each parameter. - - Returns: - A list of the split circuits along with a list of which parameters are in the subcircuits. - """ - circuits = [] - corresponding_parameters = [] - - sub = QuantumCircuit(*circuit.qregs, *circuit.cregs) - for inst in circuit.data: - # check if new split must be created - if parameters is None: - params = [ - param - for param in inst.operation.params - if isinstance(param, ParameterExpression) and len(param.parameters) > 0 - ] - else: - if inst.operation.definition is not None: - free_inst_params = inst.operation.definition.parameters - else: - free_inst_params = {} - - params = [p for p in parameters if p in free_inst_params] - - new_split = bool(len(params) > 0) - - if new_split: - sub.append(inst) - circuits.append(sub) - corresponding_parameters.append(params) - sub = QuantumCircuit(*circuit.qregs, *circuit.cregs) - else: - sub.append(inst) - - # handle leftover gates - if len(sub.data) > 0: - circuits[-1].compose(sub, inplace=True) - - return circuits, corresponding_parameters diff --git a/qiskit/algorithms/gradients/spsa/__init__.py b/qiskit/algorithms/gradients/spsa/__init__.py deleted file mode 100644 index 8f5fd2a37f84..000000000000 --- a/qiskit/algorithms/gradients/spsa/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023 -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. diff --git a/qiskit/algorithms/gradients/spsa/spsa_estimator_gradient.py b/qiskit/algorithms/gradients/spsa/spsa_estimator_gradient.py deleted file mode 100644 index 021bcb5803f0..000000000000 --- a/qiskit/algorithms/gradients/spsa/spsa_estimator_gradient.py +++ /dev/null @@ -1,135 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Gradient of Sampler with Finite difference method.""" - -from __future__ import annotations - -from collections.abc import Sequence - -import numpy as np - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseEstimator -from qiskit.providers import Options -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..base.base_estimator_gradient import BaseEstimatorGradient -from ..base.estimator_gradient_result import EstimatorGradientResult - -from ...exceptions import AlgorithmError - - -class SPSAEstimatorGradient(BaseEstimatorGradient): - """ - Compute the gradients of the expectation value by the Simultaneous Perturbation Stochastic - Approximation (SPSA) [1]. - - **Reference:** - [1] J. C. Spall, Adaptive stochastic approximation by the simultaneous perturbation method in - IEEE Transactions on Automatic Control, vol. 45, no. 10, pp. 1839-1853, Oct 2020, - `doi: 10.1109/TAC.2000.880982 `_ - """ - - def __init__( - self, - estimator: BaseEstimator, - epsilon: float, - batch_size: int = 1, - seed: int | None = None, - options: Options | None = None, - ): - """ - Args: - estimator: The estimator used to compute the gradients. - epsilon: The offset size for the SPSA gradients. - batch_size: The number of gradients to average. - seed: The seed for a random perturbation vector. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting - - Raises: - ValueError: If ``epsilon`` is not positive. - """ - if epsilon <= 0: - raise ValueError(f"epsilon ({epsilon}) should be positive.") - self._epsilon = epsilon - self._batch_size = batch_size - self._seed = np.random.default_rng(seed) - - super().__init__(estimator, options) - - def _run( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> EstimatorGradientResult: - """Compute the estimator gradients on the given circuits.""" - job_circuits, job_observables, job_param_values, metadata, offsets = [], [], [], [], [] - all_n = [] - for circuit, observable, parameter_values_, parameters_ in zip( - circuits, observables, parameter_values, parameters - ): - # Indices of parameters to be differentiated. - indices = [circuit.parameters.data.index(p) for p in parameters_] - metadata.append({"parameters": parameters_}) - # Make random perturbation vectors. - offset = [ - (-1) ** (self._seed.integers(0, 2, len(circuit.parameters))) - for _ in range(self._batch_size) - ] - plus = [parameter_values_ + self._epsilon * offset_ for offset_ in offset] - minus = [parameter_values_ - self._epsilon * offset_ for offset_ in offset] - offsets.append(offset) - - # Combine inputs into a single job to reduce overhead. - job_circuits.extend([circuit] * 2 * self._batch_size) - job_observables.extend([observable] * 2 * self._batch_size) - job_param_values.extend(plus + minus) - all_n.append(2 * self._batch_size) - - # Run the single job with all circuits. - job = self._estimator.run( - job_circuits, - job_observables, - job_param_values, - **options, - ) - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Estimator job failed.") from exc - - # Compute the gradients. - gradients = [] - partial_sum_n = 0 - for i, n in enumerate(all_n): - result = results.values[partial_sum_n : partial_sum_n + n] - partial_sum_n += n - n = len(result) // 2 - diffs = (result[:n] - result[n:]) / (2 * self._epsilon) - # Calculate the gradient for each batch. Note that (``diff`` / ``offset``) is the gradient - # since ``offset`` is a perturbation vector of 1s and -1s. - batch_gradients = np.array([diff / offset for diff, offset in zip(diffs, offsets[i])]) - # Take the average of the batch gradients. - gradient = np.mean(batch_gradients, axis=0) - indices = [circuits[i].parameters.data.index(p) for p in metadata[i]["parameters"]] - gradients.append(gradient[indices]) - - opt = self._get_local_options(options) - return EstimatorGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit/algorithms/gradients/spsa/spsa_sampler_gradient.py b/qiskit/algorithms/gradients/spsa/spsa_sampler_gradient.py deleted file mode 100644 index be23274d7ebc..000000000000 --- a/qiskit/algorithms/gradients/spsa/spsa_sampler_gradient.py +++ /dev/null @@ -1,136 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Gradient of Sampler with Finite difference method.""" - -from __future__ import annotations - -from collections import defaultdict -from collections.abc import Sequence - -import numpy as np - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.primitives import BaseSampler -from qiskit.providers import Options - -from ..base.base_sampler_gradient import BaseSamplerGradient -from ..base.sampler_gradient_result import SamplerGradientResult - -from ...exceptions import AlgorithmError - - -class SPSASamplerGradient(BaseSamplerGradient): - """ - Compute the gradients of the sampling probability by the Simultaneous Perturbation Stochastic - Approximation (SPSA) [1]. - - **Reference:** - [1] J. C. Spall, Adaptive stochastic approximation by the simultaneous perturbation method in - IEEE Transactions on Automatic Control, vol. 45, no. 10, pp. 1839-1853, Oct 2020, - `doi: 10.1109/TAC.2000.880982 `_. - """ - - def __init__( - self, - sampler: BaseSampler, - epsilon: float, - batch_size: int = 1, - seed: int | None = None, - options: Options | None = None, - ): - """ - Args: - sampler: The sampler used to compute the gradients. - epsilon: The offset size for the SPSA gradients. - batch_size: number of gradients to average. - seed: The seed for a random perturbation vector. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > gradient's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting - - Raises: - ValueError: If ``epsilon`` is not positive. - """ - if epsilon <= 0: - raise ValueError(f"epsilon ({epsilon}) should be positive.") - self._batch_size = batch_size - self._epsilon = epsilon - self._seed = np.random.default_rng(seed) - - super().__init__(sampler, options) - - def _run( - self, - circuits: Sequence[QuantumCircuit], - parameter_values: Sequence[Sequence[float]], - parameters: Sequence[Sequence[Parameter]], - **options, - ) -> SamplerGradientResult: - """Compute the sampler gradients on the given circuits.""" - job_circuits, job_param_values, metadata, offsets = [], [], [], [] - all_n = [] - for circuit, parameter_values_, parameters_ in zip(circuits, parameter_values, parameters): - # Indices of parameters to be differentiated. - indices = [circuit.parameters.data.index(p) for p in parameters_] - metadata.append({"parameters": parameters_}) - offset = np.array( - [ - (-1) ** (self._seed.integers(0, 2, len(circuit.parameters))) - for _ in range(self._batch_size) - ] - ) - plus = [parameter_values_ + self._epsilon * offset_ for offset_ in offset] - minus = [parameter_values_ - self._epsilon * offset_ for offset_ in offset] - offsets.append(offset) - - # Combine inputs into a single job to reduce overhead. - n = 2 * self._batch_size - job_circuits.extend([circuit] * n) - job_param_values.extend(plus + minus) - all_n.append(n) - - # Run the single job with all circuits. - job = self._sampler.run(job_circuits, job_param_values, **options) - try: - results = job.result() - except Exception as exc: - raise AlgorithmError("Sampler job failed.") from exc - - # Compute the gradients. - gradients = [] - partial_sum_n = 0 - for i, n in enumerate(all_n): - dist_diffs = {} - result = results.quasi_dists[partial_sum_n : partial_sum_n + n] - for j, (dist_plus, dist_minus) in enumerate(zip(result[: n // 2], result[n // 2 :])): - dist_diff: dict[int, float] = defaultdict(float) - for key, value in dist_plus.items(): - dist_diff[key] += value / (2 * self._epsilon) - for key, value in dist_minus.items(): - dist_diff[key] -= value / (2 * self._epsilon) - dist_diffs[j] = dist_diff - gradient = [] - indices = [circuits[i].parameters.data.index(p) for p in metadata[i]["parameters"]] - for j in indices: - gradient_j: dict[int, float] = defaultdict(float) - for k in range(self._batch_size): - for key, value in dist_diffs[k].items(): - gradient_j[key] += value * offsets[i][k][j] - gradient_j = {key: value / self._batch_size for key, value in gradient_j.items()} - gradient.append(gradient_j) - gradients.append(gradient) - partial_sum_n += n - - opt = self._get_local_options(options) - return SamplerGradientResult(gradients=gradients, metadata=metadata, options=opt) diff --git a/qiskit/algorithms/gradients/utils.py b/qiskit/algorithms/gradients/utils.py deleted file mode 100644 index f46911ee26ff..000000000000 --- a/qiskit/algorithms/gradients/utils.py +++ /dev/null @@ -1,375 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Utility functions for gradients -""" - -from __future__ import annotations - -from collections import defaultdict -from collections.abc import Sequence -from dataclasses import dataclass -from enum import Enum - -import numpy as np - -from qiskit.circuit import ( - ClassicalRegister, - Gate, - Instruction, - Parameter, - ParameterExpression, - QuantumCircuit, - QuantumRegister, -) -from qiskit.circuit.library.standard_gates import ( - CXGate, - CYGate, - CZGate, - RXGate, - RXXGate, - RYGate, - RYYGate, - RZGate, - RZXGate, - RZZGate, - XGate, -) -from qiskit.quantum_info import SparsePauliOp - - -################################################################################ -## Gradient circuits and Enum -################################################################################ -class DerivativeType(Enum): - """Types of derivative.""" - - REAL = "real" - IMAG = "imag" - COMPLEX = "complex" - - -@dataclass -class GradientCircuit: - """Gradient circuit with unique parameters and mapping information.""" - - gradient_circuit: QuantumCircuit - """An internal quantum circuit with unique parameters used to calculate the gradient""" - parameter_map: dict[Parameter, list[tuple[Parameter, float | ParameterExpression]]] - """A dictionary maps the parameters of ``circuit`` to the parameters of ``gradient_circuit`` with - coefficients""" - gradient_parameter_map: dict[Parameter, ParameterExpression] - """A dictionary maps the parameters of ``gradient_circuit`` to the parameter expressions of - ``circuit``""" - - -@dataclass -class LinearCombGradientCircuit: - """Gradient circuit for the linear combination of unitaries method.""" - - gradient_circuit: QuantumCircuit - """A gradient circuit for the linear combination of unitaries method.""" - coeff: float | ParameterExpression - """A coefficient corresponds to the gradient circuit.""" - - -################################################################################ -## Parameter shift gradient -################################################################################ -def _make_param_shift_parameter_values( - circuit: QuantumCircuit, - parameter_values: np.ndarray | list[float], - parameters: Sequence[Parameter], -) -> list[np.ndarray]: - """Returns a list of parameter values with offsets for parameter shift rule. - - Args: - circuit: The original quantum circuit - parameter_values: parameter values to be added to the base parameter values. - parameters: The parameters to be shifted. - - Returns: - A list of parameter values with offsets for parameter shift rule. - """ - indices = [circuit.parameters.data.index(p) for p in parameters] - offset = np.identity(circuit.num_parameters)[indices, :] - plus_offsets = parameter_values + offset * np.pi / 2 - minus_offsets = parameter_values - offset * np.pi / 2 - return plus_offsets.tolist() + minus_offsets.tolist() - - -################################################################################ -## Linear combination gradient and Linear combination QGT -################################################################################ -def _make_lin_comb_gradient_circuit( - circuit: QuantumCircuit, add_measurement: bool = False -) -> dict[Parameter, QuantumCircuit]: - """Makes a circuit that computes the linear combination of the gradient circuits.""" - circuit_temp = circuit.copy() - qr_aux = QuantumRegister(1, "qr_aux") - cr_aux = ClassicalRegister(1, "cr_aux") - circuit_temp.add_register(qr_aux) - circuit_temp.add_register(cr_aux) - circuit_temp.h(qr_aux) - circuit_temp.data.insert(0, circuit_temp.data.pop()) - circuit_temp.sdg(qr_aux) - circuit_temp.data.insert(1, circuit_temp.data.pop()) - - lin_comb_circuits = {} - for i, instruction in enumerate(circuit_temp.data): - if instruction.operation.is_parameterized(): - for p in instruction.operation.params[0].parameters: - gate = _gate_gradient(instruction.operation) - lin_comb_circuit = circuit_temp.copy() - # insert `gate` to i-th position - lin_comb_circuit.append(gate, [qr_aux[0]] + list(instruction.qubits), []) - lin_comb_circuit.data.insert(i, lin_comb_circuit.data.pop()) - lin_comb_circuit.h(qr_aux) - if add_measurement: - lin_comb_circuit.measure(qr_aux, cr_aux) - lin_comb_circuits[p] = lin_comb_circuit - - return lin_comb_circuits - - -def _gate_gradient(gate: Gate) -> Instruction: - """Returns the derivative of the gate""" - # pylint: disable=too-many-return-statements - if isinstance(gate, RXGate): - return CXGate() - if isinstance(gate, RYGate): - return CYGate() - if isinstance(gate, RZGate): - return CZGate() - if isinstance(gate, RXXGate): - cxx_circ = QuantumCircuit(3) - cxx_circ.cx(0, 1) - cxx_circ.cx(0, 2) - cxx = cxx_circ.to_instruction() - return cxx - if isinstance(gate, RYYGate): - cyy_circ = QuantumCircuit(3) - cyy_circ.cy(0, 1) - cyy_circ.cy(0, 2) - cyy = cyy_circ.to_instruction() - return cyy - if isinstance(gate, RZZGate): - czz_circ = QuantumCircuit(3) - czz_circ.cz(0, 1) - czz_circ.cz(0, 2) - czz = czz_circ.to_instruction() - return czz - if isinstance(gate, RZXGate): - czx_circ = QuantumCircuit(3) - czx_circ.cx(0, 2) - czx_circ.cz(0, 1) - czx = czx_circ.to_instruction() - return czx - raise TypeError(f"Unrecognized parameterized gate, {gate}") - - -def _make_lin_comb_qgt_circuit( - circuit: QuantumCircuit, add_measurement: bool = False -) -> dict[tuple[Parameter, Parameter], QuantumCircuit]: - """Makes a circuit that computes the linear combination of the QGT circuits.""" - circuit_temp = circuit.copy() - qr_aux = QuantumRegister(1, "aux") - circuit_temp.add_register(qr_aux) - if add_measurement: - cr_aux = ClassicalRegister(1, "aux") - circuit_temp.add_bits(cr_aux) - circuit_temp.h(qr_aux) - circuit_temp.data.insert(0, circuit_temp.data.pop()) - - lin_comb_qgt_circuits = {} - for i, instruction_i in enumerate(circuit_temp.data): - if not instruction_i.operation.is_parameterized(): - continue - for j, instruction_j in enumerate(circuit_temp.data): - if not instruction_j.operation.is_parameterized(): - continue - # Calculate the QGT of the i-th gate with respect to the j-th gate. - param_i = instruction_i.operation.params[0] - param_j = instruction_j.operation.params[0] - - for p_i in param_i.parameters: - for p_j in param_j.parameters: - if circuit_temp.parameters.data.index(p_i) > circuit_temp.parameters.data.index( - p_j - ): - continue - gate_i = _gate_gradient(instruction_i.operation) - gate_j = _gate_gradient(instruction_j.operation) - lin_comb_qgt_circuit = circuit_temp.copy() - if i < j: - # insert gate_j to j-th position - lin_comb_qgt_circuit.append( - gate_j, [qr_aux[0]] + list(instruction_j.qubits), [] - ) - lin_comb_qgt_circuit.data.insert(j, lin_comb_qgt_circuit.data.pop()) - # insert gate_i to i-th position with two X gates at its sides - lin_comb_qgt_circuit.append(XGate(), [qr_aux[0]], []) - lin_comb_qgt_circuit.data.insert(i, lin_comb_qgt_circuit.data.pop()) - lin_comb_qgt_circuit.append( - gate_i, [qr_aux[0]] + list(instruction_i.qubits), [] - ) - lin_comb_qgt_circuit.data.insert(i, lin_comb_qgt_circuit.data.pop()) - lin_comb_qgt_circuit.append(XGate(), [qr_aux[0]], []) - lin_comb_qgt_circuit.data.insert(i, lin_comb_qgt_circuit.data.pop()) - else: - # insert gate_i to i-th position - lin_comb_qgt_circuit.append( - gate_i, [qr_aux[0]] + list(instruction_i.qubits), [] - ) - lin_comb_qgt_circuit.data.insert(i, lin_comb_qgt_circuit.data.pop()) - # insert gate_j to j-th position with two X gates at its sides - lin_comb_qgt_circuit.append(XGate(), [qr_aux[0]], []) - lin_comb_qgt_circuit.data.insert(j, lin_comb_qgt_circuit.data.pop()) - lin_comb_qgt_circuit.append( - gate_j, [qr_aux[0]] + list(instruction_j.qubits), [] - ) - lin_comb_qgt_circuit.data.insert(j, lin_comb_qgt_circuit.data.pop()) - lin_comb_qgt_circuit.append(XGate(), [qr_aux[0]], []) - lin_comb_qgt_circuit.data.insert(j, lin_comb_qgt_circuit.data.pop()) - - lin_comb_qgt_circuit.h(qr_aux) - if add_measurement: - lin_comb_qgt_circuit.measure(qr_aux, cr_aux) - lin_comb_qgt_circuits[(p_i, p_j)] = lin_comb_qgt_circuit - - return lin_comb_qgt_circuits - - -def _make_lin_comb_observables( - observable: SparsePauliOp, - derivative_type: DerivativeType, -) -> tuple[SparsePauliOp, SparsePauliOp | None]: - """Make the observable with an ancillary operator for the linear combination gradient. - - Args: - observable: The observable. - derivative_type: The type of derivative. Can be either ``DerivativeType.REAL`` - ``DerivativeType.IMAG``, or ``DerivativeType.COMPLEX``. - - Returns: - The observable with an ancillary operator for the linear combination gradient. - - Raises: - ValueError: If the derivative type is not supported. - """ - if derivative_type == DerivativeType.REAL: - return observable.expand(SparsePauliOp.from_list([("Z", 1)])), None - elif derivative_type == DerivativeType.IMAG: - return observable.expand(SparsePauliOp.from_list([("Y", -1)])), None - elif derivative_type == DerivativeType.COMPLEX: - return observable.expand(SparsePauliOp.from_list([("Z", 1)])), observable.expand( - SparsePauliOp.from_list([("Y", -1)]) - ) - else: - raise ValueError(f"Derivative type {derivative_type} is not supported.") - - -################################################################################ -## Preprocess -################################################################################ -def _assign_unique_parameters( - circuit: QuantumCircuit, -) -> GradientCircuit: - """Assign unique parameters to the circuit. - - Args: - circuit: The circuit to assign unique parameters. - - Returns: - The circuit with unique parameters and the mapping from the original parameters to the - unique parameters. - """ - gradient_circuit = circuit.copy_empty_like(f"{circuit.name}_gradient") - parameter_map = defaultdict(list) - gradient_parameter_map = {} - num_gradient_parameters = 0 - for instruction in circuit.data: - if instruction.operation.is_parameterized(): - new_op_params = [] - for angle in instruction.operation.params: - new_parameter = Parameter(f"__gθ{num_gradient_parameters}") - new_op_params.append(new_parameter) - num_gradient_parameters += 1 - for parameter in angle.parameters: - parameter_map[parameter].append((new_parameter, angle.gradient(parameter))) - gradient_parameter_map[new_parameter] = angle - instruction.operation.params = new_op_params - gradient_circuit.append(instruction.operation, instruction.qubits, instruction.clbits) - # For the global phase - gradient_circuit.global_phase = circuit.global_phase - if isinstance(gradient_circuit.global_phase, ParameterExpression): - substitution_map = {} - for parameter in gradient_circuit.global_phase.parameters: - if parameter in parameter_map: - substitution_map[parameter] = parameter_map[parameter][0][0] - else: - new_parameter = Parameter(f"__gθ{num_gradient_parameters}") - substitution_map[parameter] = new_parameter - parameter_map[parameter].append((new_parameter, 1)) - num_gradient_parameters += 1 - gradient_circuit.global_phase = gradient_circuit.global_phase.subs(substitution_map) - return GradientCircuit(gradient_circuit, parameter_map, gradient_parameter_map) - - -def _make_gradient_parameter_values( - circuit: QuantumCircuit, - gradient_circuit: GradientCircuit, - parameter_values: np.ndarray, -) -> np.ndarray: - """Makes parameter values for the gradient circuit. - - Args: - circuit: The original quantum circuit - gradient_circuit: The gradient circuit - parameter_values: The parameter values for the original circuit - parameter_set: The parameter set to calculate gradients - - Returns: - The parameter values for the gradient circuit. - """ - g_circuit = gradient_circuit.gradient_circuit - g_parameter_values = np.empty(len(g_circuit.parameters)) - for i, g_parameter in enumerate(g_circuit.parameters): - expr = gradient_circuit.gradient_parameter_map[g_parameter] - bound_expr = expr.bind( - {p: parameter_values[circuit.parameters.data.index(p)] for p in expr.parameters} - ) - g_parameter_values[i] = float(bound_expr) - return g_parameter_values - - -def _make_gradient_parameters( - gradient_circuit: GradientCircuit, - parameters: Sequence[Parameter], -) -> Sequence[Parameter]: - """Makes parameter set for the gradient circuit. - - Args: - gradient_circuit: The gradient circuit - parameters: The parameters in the original circuit to calculate gradients - - Returns: - The parameters in the gradient circuit to calculate gradients. - """ - g_parameters = [ - g_parameter - for parameter in parameters - for g_parameter, _ in gradient_circuit.parameter_map[parameter] - ] - # make g_parameters unique and return it. - return list(dict.fromkeys(g_parameters)) diff --git a/qiskit/algorithms/list_or_dict.py b/qiskit/algorithms/list_or_dict.py deleted file mode 100644 index 95314dd79a3b..000000000000 --- a/qiskit/algorithms/list_or_dict.py +++ /dev/null @@ -1,18 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Introduced new type to maintain readability.""" - -from typing import TypeVar, List, Union, Optional, Dict - -_T = TypeVar("_T") # Pylint does not allow single character class names. -ListOrDict = Union[List[Optional[_T]], Dict[str, _T]] diff --git a/qiskit/algorithms/minimum_eigen_solvers/__init__.py b/qiskit/algorithms/minimum_eigen_solvers/__init__.py deleted file mode 100644 index 3d7d18023b75..000000000000 --- a/qiskit/algorithms/minimum_eigen_solvers/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Minimum Eigen Solvers Package""" - -from .vqe import VQE, VQEResult -from .qaoa import QAOA -from .numpy_minimum_eigen_solver import NumPyMinimumEigensolver -from .minimum_eigen_solver import MinimumEigensolver, MinimumEigensolverResult - -__all__ = [ - "VQE", - "VQEResult", - "QAOA", - "NumPyMinimumEigensolver", - "MinimumEigensolver", - "MinimumEigensolverResult", -] diff --git a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py b/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py deleted file mode 100644 index 6625cf30eaeb..000000000000 --- a/qiskit/algorithms/minimum_eigen_solvers/minimum_eigen_solver.py +++ /dev/null @@ -1,141 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Minimum Eigensolver interface""" -from __future__ import annotations - -from abc import ABC, abstractmethod - -import numpy as np - -from qiskit.opflow import OperatorBase -from qiskit.utils.deprecation import deprecate_func -from ..algorithm_result import AlgorithmResult -from ..list_or_dict import ListOrDict - - -class MinimumEigensolver(ABC): - """Deprecated: Minimum Eigensolver Interface. - - The Minimum Eigensolver interface has been superseded by the - :class:`qiskit.algorithms.minimum_eigensolvers.MinimumEigensolver` interface. - This interface will be deprecated in a future release and subsequently - removed after that. - - Algorithms that can compute a minimum eigenvalue for an operator - may implement this interface to allow different algorithms to be - used interchangeably. - """ - - @deprecate_func( - additional_msg=( - "Instead, use the interface " - "``qiskit.algorithms.minimum_eigensolvers.MinimumEigensolver``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__(self) -> None: - pass - - @abstractmethod - def compute_minimum_eigenvalue( - self, operator: OperatorBase, aux_operators: ListOrDict[OperatorBase] | None = None - ) -> "MinimumEigensolverResult": - """ - Computes minimum eigenvalue. Operator and aux_operators can be supplied here and - if not None will override any already set into algorithm so it can be reused with - different operators. While an operator is required by algorithms, aux_operators - are optional. To 'remove' a previous aux_operators array use an empty list here. - - Args: - operator: Qubit operator of the Observable - aux_operators: Optional list of auxiliary operators to be evaluated with the - eigenstate of the minimum eigenvalue main result and their expectation values - returned. For instance in chemistry these can be dipole operators, total particle - count operators so we can get values for these at the ground state. - - Returns: - MinimumEigensolverResult - """ - return MinimumEigensolverResult() - - @classmethod - def supports_aux_operators(cls) -> bool: - """Whether computing the expectation value of auxiliary operators is supported. - - If the minimum eigensolver computes an eigenstate of the main operator then it - can compute the expectation value of the aux_operators for that state. Otherwise - they will be ignored. - - Returns: - True if aux_operator expectations can be evaluated, False otherwise - """ - return False - - -class MinimumEigensolverResult(AlgorithmResult): - """Deprecated: Minimum Eigensolver Result. - - The MinimumEigensolverResult class has been superseded by the - :class:`qiskit.algorithms.minimum_eigensolvers.MinimumEigensolverResult` class. - This class will be deprecated in a future release and subsequently - removed after that. - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class " - "``qiskit.algorithms.minimum_eigensolvers.MinimumEigensolverResult``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__(self) -> None: - super().__init__() - self._eigenvalue: complex | None = None - self._eigenstate: np.ndarray | None = None - self._aux_operator_eigenvalues: ListOrDict[tuple[complex, complex]] | None = None - - @property - def eigenvalue(self) -> complex | None: - """returns eigen value""" - return self._eigenvalue - - @eigenvalue.setter - def eigenvalue(self, value: complex) -> None: - """set eigen value""" - self._eigenvalue = value - - @property - def eigenstate(self) -> np.ndarray | None: - """return eigen state""" - return self._eigenstate - - @eigenstate.setter - def eigenstate(self, value: np.ndarray) -> None: - """set eigen state""" - self._eigenstate = value - - @property - def aux_operator_eigenvalues(self) -> ListOrDict[tuple[complex, complex]] | None: - """Return aux operator expectation values. - - These values are in fact tuples formatted as (mean, standard deviation). - """ - return self._aux_operator_eigenvalues - - @aux_operator_eigenvalues.setter - def aux_operator_eigenvalues(self, value: ListOrDict[tuple[complex, complex]]) -> None: - """set aux operator eigen values""" - self._aux_operator_eigenvalues = value diff --git a/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py b/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py deleted file mode 100644 index 83623666b715..000000000000 --- a/qiskit/algorithms/minimum_eigen_solvers/numpy_minimum_eigen_solver.py +++ /dev/null @@ -1,105 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Numpy Minimum Eigensolver algorithm.""" -from __future__ import annotations - -import logging -import warnings -from collections.abc import Callable - -import numpy as np - -from qiskit.opflow import OperatorBase -from qiskit.utils.deprecation import deprecate_func -from ..eigen_solvers.numpy_eigen_solver import NumPyEigensolver -from .minimum_eigen_solver import MinimumEigensolver, MinimumEigensolverResult -from ..list_or_dict import ListOrDict - -logger = logging.getLogger(__name__) - - -class NumPyMinimumEigensolver(MinimumEigensolver): - """ - Deprecated: Numpy Minimum Eigensolver algorithm. - - The NumPyMinimumEigensolver class has been superseded by the - :class:`qiskit.algorithms.minimum_eigensolvers.NumPyMinimumEigensolver` class. - This class will be deprecated in a future release and subsequently - removed after that. - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class " - "``qiskit.algorithms.minimum_eigensolvers.NumPyMinimumEigensolver``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - filter_criterion: Callable[ - [list | np.ndarray, float, ListOrDict[float] | None], bool - ] = None, - ) -> None: - """ - Args: - filter_criterion: callable that allows to filter eigenvalues/eigenstates. The minimum - eigensolver is only searching over feasible states and returns an eigenstate that - has the smallest eigenvalue among feasible states. The callable has the signature - `filter(eigenstate, eigenvalue, aux_values)` and must return a boolean to indicate - whether to consider this value or not. If there is no - feasible element, the result can even be empty. - """ - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - super().__init__() - self._ces = NumPyEigensolver(filter_criterion=filter_criterion) - self._ret = MinimumEigensolverResult() - - @property - def filter_criterion( - self, - ) -> Callable[[list | np.ndarray, float, ListOrDict[float] | None], bool] | None: - """returns the filter criterion if set""" - return self._ces.filter_criterion - - @filter_criterion.setter - def filter_criterion( - self, - filter_criterion: Callable[[list | np.ndarray, float, ListOrDict[float] | None], bool] - | None, - ) -> None: - """set the filter criterion""" - self._ces.filter_criterion = filter_criterion - - @classmethod - def supports_aux_operators(cls) -> bool: - return NumPyEigensolver.supports_aux_operators() - - def compute_minimum_eigenvalue( - self, operator: OperatorBase, aux_operators: ListOrDict[OperatorBase] | None = None - ) -> MinimumEigensolverResult: - super().compute_minimum_eigenvalue(operator, aux_operators) - result_ces = self._ces.compute_eigenvalues(operator, aux_operators) - self._ret = MinimumEigensolverResult() - if result_ces.eigenvalues is not None and len(result_ces.eigenvalues) > 0: - self._ret.eigenvalue = result_ces.eigenvalues[0] - self._ret.eigenstate = result_ces.eigenstates[0] - if result_ces.aux_operator_eigenvalues: - self._ret.aux_operator_eigenvalues = result_ces.aux_operator_eigenvalues[0] - - logger.debug("MinimumEigensolver:\n%s", self._ret) - - return self._ret diff --git a/qiskit/algorithms/minimum_eigen_solvers/qaoa.py b/qiskit/algorithms/minimum_eigen_solvers/qaoa.py deleted file mode 100644 index fc18be860218..000000000000 --- a/qiskit/algorithms/minimum_eigen_solvers/qaoa.py +++ /dev/null @@ -1,185 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Quantum Approximate Optimization Algorithm.""" -from __future__ import annotations - -import warnings -from collections.abc import Callable - -import numpy as np - -from qiskit.algorithms.optimizers import Minimizer, Optimizer -from qiskit.circuit import QuantumCircuit -from qiskit.opflow import OperatorBase, ExpectationBase -from qiskit.opflow.gradients import GradientBase -from qiskit.providers import Backend -from qiskit.utils.quantum_instance import QuantumInstance -from qiskit.utils.validation import validate_min -from qiskit.utils.deprecation import deprecate_func -from qiskit.circuit.library.n_local.qaoa_ansatz import QAOAAnsatz -from qiskit.algorithms.minimum_eigen_solvers.vqe import VQE - - -class QAOA(VQE): - """ - Deprecated: Quantum Approximate Optimization Algorithm. - - The QAOA class has been superseded by the - :class:`qiskit.algorithms.minimum_eigensolvers.QAOA` class. - This class will be deprecated in a future release and subsequently - removed after that. - - `QAOA `__ is a well-known algorithm for finding approximate - solutions to combinatorial-optimization problems. - - The QAOA implementation directly extends :class:`VQE` and inherits VQE's optimization structure. - However, unlike VQE, which can be configured with arbitrary ansatzes, - QAOA uses its own fine-tuned ansatz, which comprises :math:`p` parameterized global - :math:`x` rotations and :math:`p` different parameterizations of the problem hamiltonian. - QAOA is thus principally configured by the single integer parameter, *p*, - which dictates the depth of the ansatz, and thus affects the approximation quality. - - An optional array of :math:`2p` parameter values, as the *initial_point*, may be provided as the - starting **beta** and **gamma** parameters (as identically named in the - original `QAOA paper `__) for the QAOA ansatz. - - An operator or a parameterized quantum circuit may optionally also be provided as a custom - `mixer` Hamiltonian. This allows, as discussed in - `this paper `__ for quantum annealing, - and in `this paper `__ for QAOA, - to run constrained optimization problems where the mixer constrains - the evolution to a feasible subspace of the full Hilbert space. - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.minimum_eigensolvers.QAOA``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - optimizer: Optimizer | Minimizer | None = None, - reps: int = 1, - initial_state: QuantumCircuit | None = None, - mixer: QuantumCircuit | OperatorBase = None, - initial_point: np.ndarray | None = None, - gradient: GradientBase | Callable[[np.ndarray | list], list] | None = None, - expectation: ExpectationBase | None = None, - include_custom: bool = False, - max_evals_grouped: int = 1, - callback: Callable[[int, np.ndarray, float, float], None] | None = None, - quantum_instance: QuantumInstance | Backend | None = None, - ) -> None: - """ - Args: - optimizer: A classical optimizer, see also :class:`~qiskit.algorithms.VQE` for - more details on the possible types. - reps: the integer parameter :math:`p` as specified in https://arxiv.org/abs/1411.4028, - Has a minimum valid value of 1. - initial_state: An optional initial state to prepend the QAOA circuit with - mixer: the mixer Hamiltonian to evolve with or a custom quantum circuit. Allows support - of optimizations in constrained subspaces as per https://arxiv.org/abs/1709.03489 - as well as warm-starting the optimization as introduced - in http://arxiv.org/abs/2009.10095. - initial_point: An optional initial point (i.e. initial parameter values) - for the optimizer. If ``None`` then it will simply compute a random one. - gradient: An optional gradient operator respectively a gradient function used for - optimization. - expectation: The Expectation converter for taking the average value of the - Observable over the ansatz state function. When None (the default) an - :class:`~qiskit.opflow.expectations.ExpectationFactory` is used to select - an appropriate expectation based on the operator and backend. When using Aer - qasm_simulator backend, with paulis, it is however much faster to leverage custom - Aer function for the computation but, although VQE performs much faster - with it, the outcome is ideal, with no shot noise, like using a state vector - simulator. If you are just looking for the quickest performance when choosing Aer - qasm_simulator and the lack of shot noise is not an issue then set `include_custom` - parameter here to True (defaults to False). - include_custom: When `expectation` parameter here is None setting this to True will - allow the factory to include the custom Aer pauli expectation. - max_evals_grouped: Max number of evaluations performed simultaneously. Signals the - given optimizer that more than one set of parameters can be supplied so that - potentially the expectation values can be computed in parallel. Typically this is - possible when a finite difference gradient is used by the optimizer such that - multiple points to compute the gradient can be passed and if computed in parallel - improve overall execution time. Ignored if a gradient operator or function is - given. - callback: a callback that can access the intermediate data during the optimization. - Four parameter values are passed to the callback as follows during each evaluation - by the optimizer for its current set of parameters as it works towards the minimum. - These are: the evaluation count, the optimizer parameters for the - ansatz, the evaluated mean and the evaluated standard deviation. - quantum_instance: Quantum Instance or Backend - """ - validate_min("reps", reps, 1) - - self._reps = reps - self._mixer = mixer - self._initial_state = initial_state - self._cost_operator: OperatorBase | None = None - - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - super().__init__( - ansatz=None, - optimizer=optimizer, - initial_point=initial_point, - gradient=gradient, - expectation=expectation, - include_custom=include_custom, - max_evals_grouped=max_evals_grouped, - callback=callback, - quantum_instance=quantum_instance, - ) - - def _check_operator_ansatz(self, operator: OperatorBase) -> None: - # Recreates a circuit based on operator parameter. - if operator != self._cost_operator: - self._cost_operator = operator - self.ansatz = QAOAAnsatz( - operator, self._reps, initial_state=self._initial_state, mixer_operator=self._mixer - ).decompose() # TODO remove decompose once #6674 is fixed - - @property - def initial_state(self) -> QuantumCircuit | None: - """ - Returns: - Returns the initial state. - """ - return self._initial_state - - @initial_state.setter - def initial_state(self, initial_state: QuantumCircuit | None) -> None: - """ - Args: - initial_state: Initial state to set. - """ - self._initial_state = initial_state - - @property - def mixer(self) -> QuantumCircuit | OperatorBase: - """ - Returns: - Returns the mixer. - """ - return self._mixer - - @mixer.setter - def mixer(self, mixer: QuantumCircuit | OperatorBase) -> None: - """ - Args: - mixer: Mixer to set. - """ - self._mixer = mixer diff --git a/qiskit/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/algorithms/minimum_eigen_solvers/vqe.py deleted file mode 100644 index 3ca342f9894d..000000000000 --- a/qiskit/algorithms/minimum_eigen_solvers/vqe.py +++ /dev/null @@ -1,749 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Variational Quantum Eigensolver algorithm. - -See https://arxiv.org/abs/1304.3061 -""" - -from __future__ import annotations - -import logging -import warnings -from collections.abc import Callable -from time import time - -import numpy as np - -from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.circuit.library import RealAmplitudes -from qiskit.opflow import ( - CircuitSampler, - CircuitStateFn, - ExpectationBase, - ExpectationFactory, - ListOp, - OperatorBase, - PauliSumOp, - StateFn, -) -from qiskit.opflow.gradients import GradientBase -from qiskit.providers import Backend -from qiskit.utils import QuantumInstance, algorithm_globals -from qiskit.utils.backend_utils import is_aer_provider -from qiskit.utils.validation import validate_min -from qiskit.utils.deprecation import deprecate_func - -from ..aux_ops_evaluator import eval_observables -from ..exceptions import AlgorithmError -from ..list_or_dict import ListOrDict -from ..optimizers import SLSQP, Minimizer, Optimizer -from ..variational_algorithm import VariationalAlgorithm, VariationalResult -from .minimum_eigen_solver import MinimumEigensolver, MinimumEigensolverResult - -logger = logging.getLogger(__name__) - - -class VQE(VariationalAlgorithm, MinimumEigensolver): - r"""Deprecated: Variational Quantum Eigensolver algorithm. - - The VQE class has been superseded by the - :class:`qiskit.algorithms.minimum_eigensolvers.VQE` class. - This class will be deprecated in a future release and subsequently - removed after that. - - `VQE `__ is a quantum algorithm that uses a - variational technique to find - the minimum eigenvalue of the Hamiltonian :math:`H` of a given system. - - An instance of VQE requires defining two algorithmic sub-components: - a trial state (a.k.a. ansatz) which is a :class:`QuantumCircuit`, and one of the classical - :mod:`~qiskit.algorithms.optimizers`. The ansatz is varied, via its set of parameters, by the - optimizer, such that it works towards a state, as determined by the parameters applied to the - ansatz, that will result in the minimum expectation value being measured of the input operator - (Hamiltonian). - - An optional array of parameter values, via the *initial_point*, may be provided as the - starting point for the search of the minimum eigenvalue. This feature is particularly useful - such as when there are reasons to believe that the solution point is close to a particular - point. As an example, when building the dissociation profile of a molecule, - it is likely that using the previous computed optimal solution as the starting - initial point for the next interatomic distance is going to reduce the number of iterations - necessary for the variational algorithm to converge. It provides an - `initial point tutorial `__ detailing this use case. - - The length of the *initial_point* list value must match the number of the parameters - expected by the ansatz being used. If the *initial_point* is left at the default - of ``None``, then VQE will look to the ansatz for a preferred value, based on its - given initial state. If the ansatz returns ``None``, - then a random point will be generated within the parameter bounds set, as per above. - If the ansatz provides ``None`` as the lower bound, then VQE - will default it to :math:`-2\pi`; similarly, if the ansatz returns ``None`` - as the upper bound, the default value will be :math:`2\pi`. - - The optimizer can either be one of Qiskit's optimizers, such as - :class:`~qiskit.algorithms.optimizers.SPSA` or a callable with the following signature: - - .. note:: - - The callable _must_ have the argument names ``fun, x0, jac, bounds`` as indicated - in the following code block. - - .. code-block:: python - - from qiskit.algorithms.optimizers import OptimizerResult - - def my_minimizer(fun, x0, jac=None, bounds=None) -> OptimizerResult: - # Note that the callable *must* have these argument names! - # Args: - # fun (callable): the function to minimize - # x0 (np.ndarray): the initial point for the optimization - # jac (callable, optional): the gradient of the objective function - # bounds (list, optional): a list of tuples specifying the parameter bounds - - result = OptimizerResult() - result.x = # optimal parameters - result.fun = # optimal function value - return result - - The above signature also allows to directly pass any SciPy minimizer, for instance as - - .. code-block:: python - - from functools import partial - from scipy.optimize import minimize - - optimizer = partial(minimize, method="L-BFGS-B") - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.minimum_eigensolvers.VQE``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - ansatz: QuantumCircuit | None = None, - optimizer: Optimizer | Minimizer | None = None, - initial_point: np.ndarray | None = None, - gradient: GradientBase | Callable | None = None, - expectation: ExpectationBase | None = None, - include_custom: bool = False, - max_evals_grouped: int = 1, - callback: Callable[[int, np.ndarray, float, float], None] | None = None, - quantum_instance: QuantumInstance | Backend | None = None, - ) -> None: - """ - - Args: - ansatz: A parameterized circuit used as Ansatz for the wave function. - optimizer: A classical optimizer. Can either be a Qiskit optimizer or a callable - that takes an array as input and returns a Qiskit or SciPy optimization result. - initial_point: An optional initial point (i.e. initial parameter values) - for the optimizer. If ``None`` then VQE will look to the ansatz for a preferred - point and if not will simply compute a random one. - gradient: An optional gradient function or operator for optimizer. - expectation: The Expectation converter for taking the average value of the - Observable over the ansatz state function. When ``None`` (the default) an - :class:`~qiskit.opflow.expectations.ExpectationFactory` is used to select - an appropriate expectation based on the operator and backend. When using Aer - qasm_simulator backend, with paulis, it is however much faster to leverage custom - Aer function for the computation but, although VQE performs much faster - with it, the outcome is ideal, with no shot noise, like using a state vector - simulator. If you are just looking for the quickest performance when choosing Aer - qasm_simulator and the lack of shot noise is not an issue then set `include_custom` - parameter here to ``True`` (defaults to ``False``). - include_custom: When `expectation` parameter here is None setting this to ``True`` will - allow the factory to include the custom Aer pauli expectation. - max_evals_grouped: Max number of evaluations performed simultaneously. Signals the - given optimizer that more than one set of parameters can be supplied so that - potentially the expectation values can be computed in parallel. Typically this is - possible when a finite difference gradient is used by the optimizer such that - multiple points to compute the gradient can be passed and if computed in parallel - improve overall execution time. Deprecated if a gradient operator or function is - given. - callback: a callback that can access the intermediate data during the optimization. - Four parameter values are passed to the callback as follows during each evaluation - by the optimizer for its current set of parameters as it works towards the minimum. - These are: the evaluation count, the optimizer parameters for the - ansatz, the evaluated mean and the evaluated standard deviation.` - quantum_instance: Quantum Instance or Backend - - """ - validate_min("max_evals_grouped", max_evals_grouped, 1) - - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - super().__init__() - - self._max_evals_grouped = max_evals_grouped - self._circuit_sampler: CircuitSampler | None = None - self._expectation = None - self.expectation = expectation - self._include_custom = include_custom - - self._ansatz: QuantumCircuit | None = None - self.ansatz = ansatz - - self._optimizer: Optimizer | None = None - self.optimizer = optimizer - - self._initial_point: np.ndarray | None = None - self.initial_point = initial_point - self._gradient: GradientBase | Callable | None = None - self.gradient = gradient - self._quantum_instance: QuantumInstance | None = None - - if quantum_instance is not None: - self.quantum_instance = quantum_instance - - self._eval_time = None - self._eval_count = 0 - self._callback: Callable[[int, np.ndarray, float, float], None] | None = None - self.callback = callback - - logger.info(self.print_settings()) - - # TODO remove this once the stateful methods are deleted - self._ret: VQEResult | None = None - - @property - def ansatz(self) -> QuantumCircuit: - """Returns the ansatz.""" - return self._ansatz - - @ansatz.setter - def ansatz(self, ansatz: QuantumCircuit | None): - """Sets the ansatz. - - Args: - ansatz: The parameterized circuit used as an ansatz. - If None is passed, RealAmplitudes is used by default. - - """ - if ansatz is None: - ansatz = RealAmplitudes() - - self._ansatz = ansatz - - @property - def gradient(self) -> GradientBase | Callable | None: - """Returns the gradient.""" - return self._gradient - - @gradient.setter - def gradient(self, gradient: GradientBase | Callable | None): - """Sets the gradient.""" - self._gradient = gradient - - @property - def quantum_instance(self) -> QuantumInstance | None: - """Returns quantum instance.""" - return self._quantum_instance - - @quantum_instance.setter - def quantum_instance(self, quantum_instance: QuantumInstance | Backend) -> None: - """Sets quantum_instance""" - if not isinstance(quantum_instance, QuantumInstance): - quantum_instance = QuantumInstance(quantum_instance) - - self._quantum_instance = quantum_instance - self._circuit_sampler = CircuitSampler( - quantum_instance, param_qobj=is_aer_provider(quantum_instance.backend) - ) - - @property - def initial_point(self) -> np.ndarray | None: - """Returns initial point""" - return self._initial_point - - @initial_point.setter - def initial_point(self, initial_point: np.ndarray): - """Sets initial point""" - self._initial_point = initial_point - - @property - def max_evals_grouped(self) -> int: - """Returns max_evals_grouped""" - return self._max_evals_grouped - - @max_evals_grouped.setter - def max_evals_grouped(self, max_evals_grouped: int): - """Sets max_evals_grouped""" - self._max_evals_grouped = max_evals_grouped - self.optimizer.set_max_evals_grouped(max_evals_grouped) - - @property - def include_custom(self) -> bool: - """Returns include_custom""" - return self._include_custom - - @include_custom.setter - def include_custom(self, include_custom: bool): - """Sets include_custom. If set to another value than the one that was previsously set, - the expectation attribute is reset to None. - """ - if include_custom != self._include_custom: - self._include_custom = include_custom - self.expectation = None - - @property - def callback(self) -> Callable[[int, np.ndarray, float, float], None] | None: - """Returns callback""" - return self._callback - - @callback.setter - def callback(self, callback: Callable[[int, np.ndarray, float, float], None] | None): - """Sets callback""" - self._callback = callback - - @property - def expectation(self) -> ExpectationBase | None: - """The expectation value algorithm used to construct the expectation measurement from - the observable.""" - return self._expectation - - @expectation.setter - def expectation(self, exp: ExpectationBase | None) -> None: - self._expectation = exp - - def _check_operator_ansatz(self, operator: OperatorBase): - """Check that the number of qubits of operator and ansatz match.""" - if operator is not None and self.ansatz is not None: - if operator.num_qubits != self.ansatz.num_qubits: - # try to set the number of qubits on the ansatz, if possible - try: - self.ansatz.num_qubits = operator.num_qubits - except AttributeError as ex: - raise AlgorithmError( - "The number of qubits of the ansatz does not match the " - "operator, and the ansatz does not allow setting the " - "number of qubits using `num_qubits`." - ) from ex - - @property - def optimizer(self) -> Optimizer: - """Returns optimizer""" - return self._optimizer - - @optimizer.setter - def optimizer(self, optimizer: Optimizer | None): - """Sets the optimizer attribute. - - Args: - optimizer: The optimizer to be used. If None is passed, SLSQP is used by default. - - """ - if optimizer is None: - optimizer = SLSQP() - - if isinstance(optimizer, Optimizer): - optimizer.set_max_evals_grouped(self.max_evals_grouped) - - self._optimizer = optimizer - - @property - def setting(self): - """Prepare the setting of VQE as a string.""" - ret = f"Algorithm: {self.__class__.__name__}\n" - params = "" - for key, value in self.__dict__.items(): - if key[0] == "_": - if "initial_point" in key and value is None: - params += "-- {}: {}\n".format(key[1:], "Random seed") - else: - params += f"-- {key[1:]}: {value}\n" - ret += f"{params}" - return ret - - def print_settings(self): - """ - Preparing the setting of VQE into a string. - - Returns: - str: the formatted setting of VQE - """ - ret = "\n" - ret += "==================== Setting of {} ============================\n".format( - self.__class__.__name__ - ) - ret += f"{self.setting}" - ret += "===============================================================\n" - if self.ansatz is not None: - ret += "{}".format(self.ansatz.draw(output="text")) - else: - ret += "ansatz has not been set" - ret += "===============================================================\n" - if callable(self.optimizer): - ret += "Optimizer is custom callable\n" - else: - ret += f"{self._optimizer.setting}" - ret += "===============================================================\n" - return ret - - def construct_expectation( - self, - parameter: list[float] | list[Parameter] | np.ndarray, - operator: OperatorBase, - return_expectation: bool = False, - ) -> OperatorBase | tuple[OperatorBase, ExpectationBase]: - r""" - Generate the ansatz circuit and expectation value measurement, and return their - runnable composition. - - Args: - parameter: Parameters for the ansatz circuit. - operator: Qubit operator of the Observable - return_expectation: If True, return the ``ExpectationBase`` expectation converter used - in the construction of the expectation value. Useful e.g. to compute the standard - deviation of the expectation value. - - Returns: - The Operator equalling the measurement of the ansatz :class:`StateFn` by the - Observable's expectation :class:`StateFn`, and, optionally, the expectation converter. - - Raises: - AlgorithmError: If no operator has been provided. - AlgorithmError: If no expectation is passed and None could be inferred via the - ExpectationFactory. - """ - if operator is None: - raise AlgorithmError("The operator was never provided.") - - self._check_operator_ansatz(operator) - - # if expectation was never created, try to create one - if self.expectation is None: - expectation = ExpectationFactory.build( - operator=operator, - backend=self.quantum_instance, - include_custom=self._include_custom, - ) - else: - expectation = self.expectation - - wave_function = self.ansatz.assign_parameters(parameter) - - observable_meas = expectation.convert(StateFn(operator, is_measurement=True)) - ansatz_circuit_op = CircuitStateFn(wave_function) - expect_op = observable_meas.compose(ansatz_circuit_op).reduce() - - if return_expectation: - return expect_op, expectation - - return expect_op - - def construct_circuit( - self, - parameter: list[float] | list[Parameter] | np.ndarray, - operator: OperatorBase, - ) -> list[QuantumCircuit]: - """Return the circuits used to compute the expectation value. - - Args: - parameter: Parameters for the ansatz circuit. - operator: Qubit operator of the Observable - - Returns: - A list of the circuits used to compute the expectation value. - """ - expect_op = self.construct_expectation(parameter, operator).to_circuit_op() - - circuits = [] - - # recursively extract circuits - def extract_circuits(op): - if isinstance(op, CircuitStateFn): - circuits.append(op.primitive) - elif isinstance(op, ListOp): - for op_i in op.oplist: - extract_circuits(op_i) - - extract_circuits(expect_op) - - return circuits - - @classmethod - def supports_aux_operators(cls) -> bool: - return True - - def compute_minimum_eigenvalue( - self, operator: OperatorBase, aux_operators: ListOrDict[OperatorBase] | None = None - ) -> MinimumEigensolverResult: - super().compute_minimum_eigenvalue(operator, aux_operators) - - if self.quantum_instance is None: - raise AlgorithmError( - "A QuantumInstance or Backend must be supplied to run the quantum algorithm." - ) - self.quantum_instance.circuit_summary = True - - # this sets the size of the ansatz, so it must be called before the initial point - # validation - self._check_operator_ansatz(operator) - - # set an expectation for this algorithm run (will be reset to None at the end) - initial_point = _validate_initial_point(self.initial_point, self.ansatz) - - bounds = _validate_bounds(self.ansatz) - # We need to handle the array entries being zero or Optional i.e. having value None - if aux_operators: - zero_op = PauliSumOp.from_list([("I" * self.ansatz.num_qubits, 0)]) - - # Convert the None and zero values when aux_operators is a list. - # Drop None and convert zero values when aux_operators is a dict. - if isinstance(aux_operators, list): - key_op_iterator = enumerate(aux_operators) - converted: ListOrDict[OperatorBase] = [zero_op] * len(aux_operators) - else: - key_op_iterator = aux_operators.items() - converted = {} - for key, op in key_op_iterator: - if op is not None: - converted[key] = zero_op if op == 0 else op - - aux_operators = converted - - else: - aux_operators = None - - # Convert the gradient operator into a callable function that is compatible with the - # optimization routine. - if isinstance(self._gradient, GradientBase): - gradient = self._gradient.gradient_wrapper( - ~StateFn(operator) @ StateFn(self.ansatz), - bind_params=list(self.ansatz.parameters), - backend=self._quantum_instance, - ) - else: - gradient = self._gradient - - self._eval_count = 0 - energy_evaluation, expectation = self.get_energy_evaluation( - operator, return_expectation=True - ) - - start_time = time() - - if callable(self.optimizer): - opt_result = self.optimizer( # pylint: disable=not-callable - fun=energy_evaluation, x0=initial_point, jac=gradient, bounds=bounds - ) - else: - opt_result = self.optimizer.minimize( - fun=energy_evaluation, x0=initial_point, jac=gradient, bounds=bounds - ) - - eval_time = time() - start_time - - result = VQEResult() - result.optimal_point = opt_result.x - result.optimal_parameters = dict(zip(self.ansatz.parameters, opt_result.x)) - result.optimal_value = opt_result.fun - result.cost_function_evals = opt_result.nfev - result.optimizer_time = eval_time - result.eigenvalue = opt_result.fun + 0j - result.eigenstate = self._get_eigenstate(result.optimal_parameters) - - logger.info( - "Optimization complete in %s seconds.\nFound opt_params %s in %s evals", - eval_time, - result.optimal_point, - self._eval_count, - ) - - # TODO delete as soon as get_optimal_vector etc are removed - self._ret = result - - if aux_operators is not None: - bound_ansatz = self.ansatz.assign_parameters(result.optimal_point) - - aux_values = eval_observables( - self.quantum_instance, bound_ansatz, aux_operators, expectation=expectation - ) - result.aux_operator_eigenvalues = aux_values - - return result - - def get_energy_evaluation( - self, - operator: OperatorBase, - return_expectation: bool = False, - ) -> Callable[[np.ndarray], float | list[float]] | tuple[ - Callable[[np.ndarray], float | list[float]], ExpectationBase - ]: - """Returns a function handle to evaluates the energy at given parameters for the ansatz. - - This is the objective function to be passed to the optimizer that is used for evaluation. - - Args: - operator: The operator whose energy to evaluate. - return_expectation: If True, return the ``ExpectationBase`` expectation converter used - in the construction of the expectation value. Useful e.g. to evaluate other - operators with the same expectation value converter. - - - Returns: - Energy of the hamiltonian of each parameter, and, optionally, the expectation - converter. - - Raises: - RuntimeError: If the circuit is not parameterized (i.e. has 0 free parameters). - - """ - num_parameters = self.ansatz.num_parameters - if num_parameters == 0: - raise RuntimeError("The ansatz must be parameterized, but has 0 free parameters.") - - ansatz_params = self.ansatz.parameters - expect_op, expectation = self.construct_expectation( - ansatz_params, operator, return_expectation=True - ) - - def energy_evaluation(parameters): - parameter_sets = np.reshape(parameters, (-1, num_parameters)) - # Create dict associating each parameter with the lists of parameterization values for it - param_bindings = dict(zip(ansatz_params, parameter_sets.transpose().tolist())) - - start_time = time() - sampled_expect_op = self._circuit_sampler.convert(expect_op, params=param_bindings) - means = np.real(sampled_expect_op.eval()) - - if self._callback is not None: - variance = np.real(expectation.compute_variance(sampled_expect_op)) - estimator_error = np.sqrt(variance / self.quantum_instance.run_config.shots) - for i, param_set in enumerate(parameter_sets): - self._eval_count += 1 - self._callback(self._eval_count, param_set, means[i], estimator_error[i]) - else: - self._eval_count += len(means) - - end_time = time() - logger.info( - "Energy evaluation returned %s - %.5f (ms), eval count: %s", - means, - (end_time - start_time) * 1000, - self._eval_count, - ) - - return means if len(means) > 1 else means[0] - - if return_expectation: - return energy_evaluation, expectation - - return energy_evaluation - - def _get_eigenstate(self, optimal_parameters) -> list[float] | dict[str, int]: - """Get the simulation outcome of the ansatz, provided with parameters.""" - optimal_circuit = self.ansatz.assign_parameters(optimal_parameters) - state_fn = self._circuit_sampler.convert(StateFn(optimal_circuit)).eval() - if self.quantum_instance.is_statevector: - state = state_fn.primitive.data # VectorStateFn -> Statevector -> np.array - else: - state = state_fn.to_dict_fn().primitive # SparseVectorStateFn -> DictStateFn -> dict - - return state - - -class VQEResult(VariationalResult, MinimumEigensolverResult): - """Deprecated: VQE Result. - - The VQEResult class has been superseded by the - :class:`qiskit.algorithms.minimum_eigensolvers.VQEResult` class. - This class will be deprecated in a future release and subsequently - removed after that. - - """ - - @deprecate_func( - additional_msg=( - "Instead, use the class ``qiskit.algorithms.minimum_eigensolvers.VQEResult``. " - "See https://qisk.it/algo_migration for a migration guide." - ), - since="0.24.0", - ) - def __init__(self) -> None: - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - super().__init__() - self._cost_function_evals: int | None = None - - @property - def cost_function_evals(self) -> int | None: - """Returns number of cost optimizer evaluations""" - return self._cost_function_evals - - @cost_function_evals.setter - def cost_function_evals(self, value: int) -> None: - """Sets number of cost function evaluations""" - self._cost_function_evals = value - - @property - def eigenstate(self) -> np.ndarray | None: - """return eigen state""" - return self._eigenstate - - @eigenstate.setter - def eigenstate(self, value: np.ndarray) -> None: - """set eigen state""" - self._eigenstate = value - - -def _validate_initial_point(point, ansatz): - expected_size = ansatz.num_parameters - - # try getting the initial point from the ansatz - if point is None and hasattr(ansatz, "preferred_init_points"): - point = ansatz.preferred_init_points - # if the point is None choose a random initial point - - if point is None: - # get bounds if ansatz has them set, otherwise use [-2pi, 2pi] for each parameter - bounds = getattr(ansatz, "parameter_bounds", None) - if bounds is None: - bounds = [(-2 * np.pi, 2 * np.pi)] * expected_size - - # replace all Nones by [-2pi, 2pi] - lower_bounds = [] - upper_bounds = [] - for lower, upper in bounds: - lower_bounds.append(lower if lower is not None else -2 * np.pi) - upper_bounds.append(upper if upper is not None else 2 * np.pi) - - # sample from within bounds - point = algorithm_globals.random.uniform(lower_bounds, upper_bounds) - - elif len(point) != expected_size: - raise ValueError( - f"The dimension of the initial point ({len(point)}) does not match the " - f"number of parameters in the circuit ({expected_size})." - ) - - return point - - -def _validate_bounds(ansatz): - if hasattr(ansatz, "parameter_bounds") and ansatz.parameter_bounds is not None: - bounds = ansatz.parameter_bounds - if len(bounds) != ansatz.num_parameters: - raise ValueError( - f"The number of bounds ({len(bounds)}) does not match the number of " - f"parameters in the circuit ({ansatz.num_parameters})." - ) - else: - bounds = [(None, None)] * ansatz.num_parameters - - return bounds diff --git a/qiskit/algorithms/minimum_eigensolvers/__init__.py b/qiskit/algorithms/minimum_eigensolvers/__init__.py deleted file mode 100644 index d7406c09860b..000000000000 --- a/qiskit/algorithms/minimum_eigensolvers/__init__.py +++ /dev/null @@ -1,66 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -============================================================================ -Minimum Eigensolvers Package (:mod:`qiskit.algorithms.minimum_eigensolvers`) -============================================================================ - -.. currentmodule:: qiskit.algorithms.minimum_eigensolvers - -Minimum Eigensolvers -==================== -.. autosummary:: - :toctree: ../stubs/ - - MinimumEigensolver - NumPyMinimumEigensolver - VQE - AdaptVQE - SamplingMinimumEigensolver - SamplingVQE - QAOA - -.. autosummary:: - :toctree: ../stubs/ - - MinimumEigensolverResult - NumPyMinimumEigensolverResult - VQEResult - AdaptVQEResult - SamplingMinimumEigensolverResult - SamplingVQEResult -""" - -from .adapt_vqe import AdaptVQE, AdaptVQEResult -from .minimum_eigensolver import MinimumEigensolver, MinimumEigensolverResult -from .numpy_minimum_eigensolver import NumPyMinimumEigensolver, NumPyMinimumEigensolverResult -from .vqe import VQE, VQEResult -from .sampling_mes import SamplingMinimumEigensolver, SamplingMinimumEigensolverResult -from .sampling_vqe import SamplingVQE, SamplingVQEResult -from .qaoa import QAOA - -__all__ = [ - "AdaptVQE", - "AdaptVQEResult", - "MinimumEigensolver", - "MinimumEigensolverResult", - "NumPyMinimumEigensolver", - "NumPyMinimumEigensolverResult", - "VQE", - "VQEResult", - "SamplingMinimumEigensolver", - "SamplingMinimumEigensolverResult", - "SamplingVQE", - "SamplingVQEResult", - "QAOA", -] diff --git a/qiskit/algorithms/minimum_eigensolvers/adapt_vqe.py b/qiskit/algorithms/minimum_eigensolvers/adapt_vqe.py deleted file mode 100644 index 4398d63e5107..000000000000 --- a/qiskit/algorithms/minimum_eigensolvers/adapt_vqe.py +++ /dev/null @@ -1,419 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""An implementation of the AdaptVQE algorithm.""" -from __future__ import annotations - -from collections.abc import Sequence -from enum import Enum - -import re -import logging -import warnings -from typing import Any - -import numpy as np - -from qiskit import QiskitError -from qiskit.algorithms.list_or_dict import ListOrDict -from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.opflow import OperatorBase, PauliSumOp -from qiskit.circuit.library import EvolvedOperatorAnsatz -from qiskit.utils.deprecation import deprecate_arg, deprecate_func -from qiskit.utils.validation import validate_min - -from .minimum_eigensolver import MinimumEigensolver -from .vqe import VQE, VQEResult -from ..observables_evaluator import estimate_observables -from ..variational_algorithm import VariationalAlgorithm - - -logger = logging.getLogger(__name__) - - -class TerminationCriterion(Enum): - """A class enumerating the various finishing criteria.""" - - CONVERGED = "Threshold converged" - CYCLICITY = "Aborted due to a cyclic selection of evolution operators" - MAXIMUM = "Maximum number of iterations reached" - - -class AdaptVQE(VariationalAlgorithm, MinimumEigensolver): - """The Adaptive Variational Quantum Eigensolver algorithm. - - `AdaptVQE `__ is a quantum algorithm which creates a compact - ansatz from a set of evolution operators. It iteratively extends the ansatz circuit, by - selecting the building block that leads to the largest gradient from a set of candidates. In - chemistry, this is usually a list of orbital excitations. Thus, a common choice of ansatz to be - used with this algorithm is the Unitary Coupled Cluster ansatz implemented in Qiskit Nature. - This results in a wavefunction ansatz which is uniquely adapted to the operator whose minimum - eigenvalue is being determined. This class relies on a supplied instance of :class:`~.VQE` to - find the minimum eigenvalue. The performance of AdaptVQE significantly depends on the - minimization routine. - - .. code-block:: python - - from qiskit.algorithms.minimum_eigensolvers import AdaptVQE, VQE - from qiskit.algorithms.optimizers import SLSQP - from qiskit.primitives import Estimator - from qiskit.circuit.library import EvolvedOperatorAnsatz - - # get your Hamiltonian - hamiltonian = ... - - # construct your ansatz - ansatz = EvolvedOperatorAnsatz(...) - - vqe = VQE(Estimator(), ansatz, SLSQP()) - - adapt_vqe = AdaptVQE(vqe) - - eigenvalue, _ = adapt_vqe.compute_minimum_eigenvalue(hamiltonian) - - The following attributes can be set via the initializer but can also be read and updated once - the AdaptVQE object has been constructed. - - Attributes: - solver: a :class:`~.VQE` instance used internally to compute the minimum eigenvalues. - It is a requirement that the :attr:`~.VQE.ansatz` of this solver is of type - :class:`~qiskit.circuit.library.EvolvedOperatorAnsatz`. - gradient_threshold: once all gradients have an absolute value smaller than this threshold, - the algorithm has converged and terminates. - eigenvalue_threshold: once the eigenvalue has changed by less than this threshold from one - iteration to the next, the algorithm has converged and terminates. When this case - occurs, the excitation included in the final iteration did not result in a significant - improvement of the eigenvalue and, thus, the results from this iteration are not - considered. - max_iterations: the maximum number of iterations for the adaptive loop. If ``None``, the - algorithm is not bound in its number of iterations. - """ - - @deprecate_arg( - "threshold", - since="0.24.0", - pending=True, - new_alias="gradient_threshold", - ) - def __init__( - self, - solver: VQE, - *, - gradient_threshold: float = 1e-5, - eigenvalue_threshold: float = 1e-5, - max_iterations: int | None = None, - threshold: float | None = None, # pylint: disable=unused-argument - ) -> None: - """ - Args: - solver: a :class:`~.VQE` instance used internally to compute the minimum eigenvalues. - It is a requirement that the :attr:`~.VQE.ansatz` of this solver is of type - :class:`~qiskit.circuit.library.EvolvedOperatorAnsatz`. - gradient_threshold: once all gradients have an absolute value smaller than this - threshold, the algorithm has converged and terminates. - eigenvalue_threshold: once the eigenvalue has changed by less than this threshold from - one iteration to the next, the algorithm has converged and terminates. When this - case occurs, the excitation included in the final iteration did not result in a - significant improvement of the eigenvalue and, thus, the results from this iteration - are not considered. - max_iterations: the maximum number of iterations for the adaptive loop. If ``None``, the - algorithm is not bound in its number of iterations. - threshold: once all gradients have an absolute value smaller than this threshold, the - algorithm has converged and terminates. Defaults to ``1e-5``. - """ - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - validate_min("gradient_threshold", gradient_threshold, 1e-15) - validate_min("eigenvalue_threshold", eigenvalue_threshold, 1e-15) - - self.solver = solver - self.gradient_threshold = gradient_threshold - self.eigenvalue_threshold = eigenvalue_threshold - self.max_iterations = max_iterations - self._tmp_ansatz: EvolvedOperatorAnsatz | None = None - self._excitation_pool: list[OperatorBase] = [] - self._excitation_list: list[OperatorBase] = [] - - @property - @deprecate_func( - since="0.24.0", - pending=True, - is_property=True, - additional_msg="Instead, use the gradient_threshold attribute.", - ) - def threshold(self) -> float: - """The threshold for the gradients. - - Once all gradients have an absolute value smaller than this threshold, the algorithm has - converged and terminates. - """ - return self.gradient_threshold - - @threshold.setter - @deprecate_func( - since="0.24.0", - pending=True, - is_property=True, - additional_msg="Instead, use the gradient_threshold attribute.", - ) - def threshold(self, threshold: float) -> None: - self.gradient_threshold = threshold - - @property - def initial_point(self) -> Sequence[float] | None: - """Returns the initial point of the internal :class:`~.VQE` solver.""" - return self.solver.initial_point - - @initial_point.setter - def initial_point(self, value: Sequence[float] | None) -> None: - """Sets the initial point of the internal :class:`~.VQE` solver.""" - self.solver.initial_point = value - - @classmethod - def supports_aux_operators(cls) -> bool: - return True - - def _compute_gradients( - self, - theta: list[float], - operator: BaseOperator | OperatorBase, - ) -> list[tuple[complex, dict[str, Any]]]: - """ - Computes the gradients for all available excitation operators. - - Args: - theta: List of (up to now) optimal parameters. - operator: operator whose gradient needs to be computed. - Returns: - List of pairs consisting of the computed gradient and excitation operator. - """ - # The excitations operators are applied later as exp(i*theta*excitation). - # For this commutator, we need to explicitly pull in the imaginary phase. - commutators = [1j * (operator @ exc - exc @ operator) for exc in self._excitation_pool] - res = estimate_observables(self.solver.estimator, self.solver.ansatz, commutators, theta) - return res - - @staticmethod - def _check_cyclicity(indices: list[int]) -> bool: - """ - Auxiliary function to check for cycles in the indices of the selected excitations. - - Args: - indices: The list of chosen gradient indices. - - Returns: - Whether repeating sequences of indices have been detected. - """ - cycle_regex = re.compile(r"(\b.+ .+\b)( \b\1\b)+") - # reg-ex explanation: - # 1. (\b.+ .+\b) will match at least two numbers and try to match as many as possible. The - # word boundaries in the beginning and end ensure that now numbers are split into digits. - # 2. the match of this part is placed into capture group 1 - # 3. ( \b\1\b)+ will match a space followed by the contents of capture group 1 (again - # delimited by word boundaries to avoid separation into digits). - # -> this results in any sequence of at least two numbers being detected - match = cycle_regex.search(" ".join(map(str, indices))) - logger.debug("Cycle detected: %s", match) - # Additionally we also need to check whether the last two numbers are identical, because the - # reg-ex above will only find cycles of at least two consecutive numbers. - # It is sufficient to assert that the last two numbers are different due to the iterative - # nature of the algorithm. - return match is not None or (len(indices) > 1 and indices[-2] == indices[-1]) - - def compute_minimum_eigenvalue( - self, - operator: BaseOperator | PauliSumOp, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - ) -> AdaptVQEResult: - """Computes the minimum eigenvalue. - - Args: - operator: Operator whose minimum eigenvalue we want to find. - aux_operators: Additional auxiliary operators to evaluate. - - Raises: - TypeError: If an ansatz other than :class:`~.EvolvedOperatorAnsatz` is provided. - QiskitError: If all evaluated gradients lie below the convergence threshold in the first - iteration of the algorithm. - - Returns: - An :class:`~.AdaptVQEResult` which is a :class:`~.VQEResult` but also but also - includes runtime information about the AdaptVQE algorithm like the number of iterations, - termination criterion, and the final maximum gradient. - """ - if not isinstance(self.solver.ansatz, EvolvedOperatorAnsatz): - raise TypeError("The AdaptVQE ansatz must be of the EvolvedOperatorAnsatz type.") - - # Overwrite the solver's ansatz with the initial state - self._tmp_ansatz = self.solver.ansatz - self._excitation_pool = self._tmp_ansatz.operators - self.solver.ansatz = self._tmp_ansatz.initial_state - - prev_op_indices: list[int] = [] - prev_raw_vqe_result: VQEResult | None = None - raw_vqe_result: VQEResult | None = None - theta: list[float] = [] - max_grad: tuple[complex, dict[str, Any] | None] = (0.0, None) - self._excitation_list = [] - history: list[complex] = [] - iteration = 0 - while self.max_iterations is None or iteration < self.max_iterations: - iteration += 1 - logger.info("--- Iteration #%s ---", str(iteration)) - # compute gradients - logger.debug("Computing gradients") - cur_grads = self._compute_gradients(theta, operator) - # pick maximum gradient - max_grad_index, max_grad = max( - enumerate(cur_grads), key=lambda item: np.abs(item[1][0]) - ) - logger.info( - "Found maximum gradient %s at index %s", - str(np.abs(max_grad[0])), - str(max_grad_index), - ) - # log gradients - if np.abs(max_grad[0]) < self.gradient_threshold: - if iteration == 1: - raise QiskitError( - "All gradients have been evaluated to lie below the convergence threshold " - "during the first iteration of the algorithm. Try to either tighten the " - "convergence threshold or pick a different ansatz." - ) - logger.info( - "AdaptVQE terminated successfully with a final maximum gradient: %s", - str(np.abs(max_grad[0])), - ) - termination_criterion = TerminationCriterion.CONVERGED - break - # store maximum gradient's index for cycle detection - prev_op_indices.append(max_grad_index) - # check indices of picked gradients for cycles - if self._check_cyclicity(prev_op_indices): - logger.info("Alternating sequence found. Finishing.") - logger.info("Final maximum gradient: %s", str(np.abs(max_grad[0]))) - termination_criterion = TerminationCriterion.CYCLICITY - break - # add new excitation to self._ansatz - logger.info( - "Adding new operator to the ansatz: %s", str(self._excitation_pool[max_grad_index]) - ) - self._excitation_list.append(self._excitation_pool[max_grad_index]) - theta.append(0.0) - # setting up the ansatz for the VQE iteration - self._tmp_ansatz.operators = self._excitation_list - self.solver.ansatz = self._tmp_ansatz - self.solver.initial_point = theta - # evaluating the eigenvalue with the internal VQE - prev_raw_vqe_result = raw_vqe_result - raw_vqe_result = self.solver.compute_minimum_eigenvalue(operator) - theta = raw_vqe_result.optimal_point.tolist() - # checking convergence based on the change in eigenvalue - if iteration > 1: - eigenvalue_diff = np.abs(raw_vqe_result.eigenvalue - history[-1]) - if eigenvalue_diff < self.eigenvalue_threshold: - logger.info( - "AdaptVQE terminated successfully with a final change in eigenvalue: %s", - str(eigenvalue_diff), - ) - termination_criterion = TerminationCriterion.CONVERGED - logger.debug( - "Reverting the addition of the last excitation to the ansatz since it " - "resulted in a change of the eigenvalue below the configured threshold." - ) - self._excitation_list.pop() - theta.pop() - self._tmp_ansatz.operators = self._excitation_list - self.solver.ansatz = self._tmp_ansatz - self.solver.initial_point = theta - raw_vqe_result = prev_raw_vqe_result - break - # appending the computed eigenvalue to the tracking history - history.append(raw_vqe_result.eigenvalue) - logger.info("Current eigenvalue: %s", str(raw_vqe_result.eigenvalue)) - else: - # reached maximum number of iterations - termination_criterion = TerminationCriterion.MAXIMUM - logger.info("Maximum number of iterations reached. Finishing.") - logger.info("Final maximum gradient: %s", str(np.abs(max_grad[0]))) - - result = AdaptVQEResult() - result.combine(raw_vqe_result) - result.num_iterations = iteration - result.final_max_gradient = max_grad[0] - result.termination_criterion = termination_criterion - result.eigenvalue_history = history - - # once finished evaluate auxiliary operators if any - if aux_operators is not None: - aux_values = estimate_observables( - self.solver.estimator, self.solver.ansatz, aux_operators, result.optimal_point - ) - result.aux_operators_evaluated = aux_values - - logger.info("The final eigenvalue is: %s", str(result.eigenvalue)) - self.solver.ansatz.operators = self._excitation_pool - return result - - -class AdaptVQEResult(VQEResult): - """AdaptVQE Result.""" - - def __init__(self) -> None: - super().__init__() - self._num_iterations: int | None = None - self._final_max_gradient: float | None = None - self._termination_criterion: str = "" - self._eigenvalue_history: list[float] | None = None - - @property - def num_iterations(self) -> int: - """Returns the number of iterations.""" - return self._num_iterations - - @num_iterations.setter - def num_iterations(self, value: int) -> None: - """Sets the number of iterations.""" - self._num_iterations = value - - @property - def final_max_gradient(self) -> float: - """Returns the final maximum gradient.""" - return self._final_max_gradient - - @final_max_gradient.setter - def final_max_gradient(self, value: float) -> None: - """Sets the final maximum gradient.""" - self._final_max_gradient = value - - @property - def termination_criterion(self) -> str: - """Returns the termination criterion.""" - return self._termination_criterion - - @termination_criterion.setter - def termination_criterion(self, value: str) -> None: - """Sets the termination criterion.""" - self._termination_criterion = value - - @property - def eigenvalue_history(self) -> list[float]: - """Returns the history of computed eigenvalues. - - The history's length matches the number of iterations and includes the final computed value. - """ - return self._eigenvalue_history - - @eigenvalue_history.setter - def eigenvalue_history(self, eigenvalue_history: list[float]) -> None: - """Sets the history of computed eigenvalues.""" - self._eigenvalue_history = eigenvalue_history diff --git a/qiskit/algorithms/minimum_eigensolvers/diagonal_estimator.py b/qiskit/algorithms/minimum_eigensolvers/diagonal_estimator.py deleted file mode 100644 index 40354c884d98..000000000000 --- a/qiskit/algorithms/minimum_eigensolvers/diagonal_estimator.py +++ /dev/null @@ -1,199 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Expectation value for a diagonal observable using a sampler primitive.""" - -from __future__ import annotations - -from collections.abc import Callable, Sequence, Mapping -from typing import Any - -from dataclasses import dataclass - -import numpy as np -from qiskit.algorithms.algorithm_job import AlgorithmJob -from qiskit.circuit import QuantumCircuit -from qiskit.primitives import BaseSampler, BaseEstimator, EstimatorResult -from qiskit.primitives.utils import init_observable, _circuit_key -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info import SparsePauliOp -from qiskit.quantum_info.operators.base_operator import BaseOperator - - -@dataclass(frozen=True) -class _DiagonalEstimatorResult(EstimatorResult): - """A result from an expectation of a diagonal observable.""" - - # TODO make each measurement a dataclass rather than a dict - best_measurements: Sequence[Mapping[str, Any]] | None = None - - -class _DiagonalEstimator(BaseEstimator): - """An estimator for diagonal observables.""" - - def __init__( - self, - sampler: BaseSampler, - aggregation: float | Callable[[Sequence[tuple[float, float]]], float] | None = None, - callback: Callable[[Sequence[Mapping[str, Any]]], None] | None = None, - **options, - ) -> None: - r"""Evaluate the expectation of quantum state with respect to a diagonal operator. - - Args: - sampler: The sampler used to evaluate the circuits. - aggregation: The aggregation function to aggregate the measurement outcomes. If a float - this specified the CVaR :math:`\alpha` parameter. - callback: A callback which is given the best measurements of all circuits in each - evaluation. - run_options: Options for the sampler. - - """ - super().__init__(options=options) - self.sampler = sampler - if not callable(aggregation): - aggregation = _get_cvar_aggregation(aggregation) - - self.aggregation = aggregation - self.callback = callback - self._circuit_ids = {} - self._observable_ids = {} - - def _run( - self, - circuits: Sequence[QuantumCircuit], - observables: Sequence[BaseOperator | PauliSumOp], - parameter_values: Sequence[Sequence[float]], - **run_options, - ) -> AlgorithmJob: - circuit_indices = [] - for circuit in circuits: - key = _circuit_key(circuit) - index = self._circuit_ids.get(key) - if index is not None: - circuit_indices.append(index) - else: - circuit_indices.append(len(self._circuits)) - self._circuit_ids[key] = len(self._circuits) - self._circuits.append(circuit) - self._parameters.append(circuit.parameters) - observable_indices = [] - for observable in observables: - index = self._observable_ids.get(id(observable)) - if index is not None: - observable_indices.append(index) - else: - observable_indices.append(len(self._observables)) - self._observable_ids[id(observable)] = len(self._observables) - converted_observable = init_observable(observable) - _check_observable_is_diagonal(converted_observable) # check it's diagonal - self._observables.append(converted_observable) - job = AlgorithmJob( - self._call, circuit_indices, observable_indices, parameter_values, **run_options - ) - job.submit() - return job - - def _call( - self, - circuits: Sequence[int], - observables: Sequence[int], - parameter_values: Sequence[Sequence[float]], - **run_options, - ) -> _DiagonalEstimatorResult: - job = self.sampler.run( - [self._circuits[i] for i in circuits], - parameter_values, - **run_options, - ) - sampler_result = job.result() - samples = sampler_result.quasi_dists - - # a list of dictionaries containing: {state: (measurement probability, value)} - evaluations = [ - { - state: (probability, _evaluate_sparsepauli(state, self._observables[i])) - for state, probability in sampled.items() - } - for i, sampled in zip(observables, samples) - ] - - results = np.array([self.aggregation(evaluated.values()) for evaluated in evaluations]) - - # get the best measurements - best_measurements = [] - num_qubits = self._circuits[0].num_qubits - for evaluated in evaluations: - best_result = min(evaluated.items(), key=lambda x: x[1][1]) - best_measurements.append( - { - "state": best_result[0], - "bitstring": bin(best_result[0])[2:].zfill(num_qubits), - "value": best_result[1][1], - "probability": best_result[1][0], - } - ) - - if self.callback is not None: - self.callback(best_measurements) - - return _DiagonalEstimatorResult( - values=results, metadata=sampler_result.metadata, best_measurements=best_measurements - ) - - -def _get_cvar_aggregation(alpha): - """Get the aggregation function for CVaR with confidence level ``alpha``.""" - if alpha is None: - alpha = 1 - elif not 0 <= alpha <= 1: - raise ValueError(f"alpha must be in [0, 1] but was {alpha}") - - # if alpha is close to 1 we can avoid the sorting - if np.isclose(alpha, 1): - - def aggregate(measurements): - return sum(probability * value for probability, value in measurements) - - else: - - def aggregate(measurements): - # sort by values - sorted_measurements = sorted(measurements, key=lambda x: x[1]) - - accumulated_percent = 0 # once alpha is reached, stop - cvar = 0 - for probability, value in sorted_measurements: - cvar += value * min(probability, alpha - accumulated_percent) - accumulated_percent += probability - if accumulated_percent >= alpha: - break - - return cvar / alpha - - return aggregate - - -_PARITY = np.array([-1 if bin(i).count("1") % 2 else 1 for i in range(256)], dtype=np.complex128) - - -def _evaluate_sparsepauli(state: int, observable: SparsePauliOp) -> complex: - packed_uint8 = np.packbits(observable.paulis.z, axis=1, bitorder="little") - state_bytes = np.frombuffer(state.to_bytes(packed_uint8.shape[1], "little"), dtype=np.uint8) - reduced = np.bitwise_xor.reduce(packed_uint8 & state_bytes, axis=1) - return np.sum(observable.coeffs * _PARITY[reduced]) - - -def _check_observable_is_diagonal(observable: SparsePauliOp) -> None: - is_diagonal = not np.any(observable.paulis.x) - if not is_diagonal: - raise ValueError("The observable must be diagonal.") diff --git a/qiskit/algorithms/minimum_eigensolvers/minimum_eigensolver.py b/qiskit/algorithms/minimum_eigensolvers/minimum_eigensolver.py deleted file mode 100644 index 26087c053aef..000000000000 --- a/qiskit/algorithms/minimum_eigensolvers/minimum_eigensolver.py +++ /dev/null @@ -1,97 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The minimum eigensolver interface and result.""" - -from __future__ import annotations - -from abc import ABC, abstractmethod -from typing import Any - -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..algorithm_result import AlgorithmResult -from ..list_or_dict import ListOrDict - - -class MinimumEigensolver(ABC): - """The minimum eigensolver interface. - - Algorithms that can compute a minimum eigenvalue for an operator may implement this interface to - allow different algorithms to be used interchangeably. - """ - - @abstractmethod - def compute_minimum_eigenvalue( - self, - operator: BaseOperator | PauliSumOp, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - ) -> "MinimumEigensolverResult": - """ - Computes the minimum eigenvalue. The ``operator`` and ``aux_operators`` are supplied here. - While an ``operator`` is required by algorithms, ``aux_operators`` are optional. - - Args: - operator: Qubit operator of the observable. - aux_operators: Optional list of auxiliary operators to be evaluated with the - parameters of the minimum eigenvalue main result and their expectation values - returned. For instance in chemistry these can be dipole operators and total particle - count operators, so we can get values for these at the ground state. - - Returns: - A minimum eigensolver result. - """ - return MinimumEigensolverResult() - - @classmethod - def supports_aux_operators(cls) -> bool: - """Whether computing the expectation value of auxiliary operators is supported. - - If the minimum eigensolver computes an eigenvalue of the main ``operator`` then it can - compute the expectation value of the ``aux_operators`` for that state. Otherwise they will - be ignored. - - Returns: - True if aux_operator expectations can be evaluated, False otherwise - """ - return False - - -class MinimumEigensolverResult(AlgorithmResult): - """Minimum eigensolver result.""" - - def __init__(self) -> None: - super().__init__() - self._eigenvalue: complex | None = None - self._aux_operators_evaluated: ListOrDict[tuple[complex, dict[str, Any]]] | None = None - - @property - def eigenvalue(self) -> complex | None: - """The computed minimum eigenvalue.""" - return self._eigenvalue - - @eigenvalue.setter - def eigenvalue(self, value: complex) -> None: - self._eigenvalue = value - - @property - def aux_operators_evaluated(self) -> ListOrDict[tuple[complex, dict[str, Any]]] | None: - """The aux operator expectation values. - - These values are in fact tuples formatted as (mean, (variance, shots)). - """ - return self._aux_operators_evaluated - - @aux_operators_evaluated.setter - def aux_operators_evaluated(self, value: ListOrDict[tuple[complex, dict[str, Any]]]) -> None: - self._aux_operators_evaluated = value diff --git a/qiskit/algorithms/minimum_eigensolvers/numpy_minimum_eigensolver.py b/qiskit/algorithms/minimum_eigensolvers/numpy_minimum_eigensolver.py deleted file mode 100644 index 93dc328b282c..000000000000 --- a/qiskit/algorithms/minimum_eigensolvers/numpy_minimum_eigensolver.py +++ /dev/null @@ -1,106 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The NumPy minimum eigensolver algorithm and result.""" - -from __future__ import annotations - -from typing import Callable, List, Union, Optional -import logging -import numpy as np - -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info import Statevector -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..eigensolvers.numpy_eigensolver import NumPyEigensolver -from .minimum_eigensolver import MinimumEigensolver, MinimumEigensolverResult -from ..list_or_dict import ListOrDict - -logger = logging.getLogger(__name__) - -# future type annotations not supported in type aliases in 3.8 -FilterType = Callable[[Union[List, np.ndarray], float, Optional[ListOrDict[float]]], bool] - - -class NumPyMinimumEigensolver(MinimumEigensolver): - """ - The NumPy minimum eigensolver algorithm. - """ - - def __init__( - self, - filter_criterion: FilterType | None = None, - ) -> None: - """ - Args: - filter_criterion: Callable that allows to filter eigenvalues/eigenstates. The minimum - eigensolver is only searching over feasible states and returns an eigenstate that - has the smallest eigenvalue among feasible states. The callable has the signature - ``filter(eigenstate, eigenvalue, aux_values)`` and must return a boolean to indicate - whether to consider this value or not. If there is no feasible element, the result - can even be empty. - """ - self._eigensolver = NumPyEigensolver(filter_criterion=filter_criterion) - - @property - def filter_criterion( - self, - ) -> FilterType | None: - """Returns the criterion for filtering eigenstates/eigenvalues.""" - return self._eigensolver.filter_criterion - - @filter_criterion.setter - def filter_criterion( - self, - filter_criterion: FilterType, - ) -> None: - self._eigensolver.filter_criterion = filter_criterion - - @classmethod - def supports_aux_operators(cls) -> bool: - return NumPyEigensolver.supports_aux_operators() - - def compute_minimum_eigenvalue( - self, - operator: BaseOperator | PauliSumOp, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - ) -> NumPyMinimumEigensolverResult: - super().compute_minimum_eigenvalue(operator, aux_operators) - eigensolver_result = self._eigensolver.compute_eigenvalues(operator, aux_operators) - result = NumPyMinimumEigensolverResult() - if eigensolver_result.eigenvalues is not None and len(eigensolver_result.eigenvalues) > 0: - result.eigenvalue = eigensolver_result.eigenvalues[0] - result.eigenstate = eigensolver_result.eigenstates[0] - if eigensolver_result.aux_operators_evaluated: - result.aux_operators_evaluated = eigensolver_result.aux_operators_evaluated[0] - - logger.debug("NumPy minimum eigensolver result: %s", result) - - return result - - -class NumPyMinimumEigensolverResult(MinimumEigensolverResult): - """NumPy minimum eigensolver result.""" - - def __init__(self) -> None: - super().__init__() - self._eigenstate: Statevector | None = None - - @property - def eigenstate(self) -> Statevector | None: - """Returns the eigenstate corresponding to the computed minimum eigenvalue.""" - return self._eigenstate - - @eigenstate.setter - def eigenstate(self, value: Statevector) -> None: - self._eigenstate = value diff --git a/qiskit/algorithms/minimum_eigensolvers/qaoa.py b/qiskit/algorithms/minimum_eigensolvers/qaoa.py deleted file mode 100644 index 825d4fa64cc7..000000000000 --- a/qiskit/algorithms/minimum_eigensolvers/qaoa.py +++ /dev/null @@ -1,144 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The quantum approximate optimization algorithm.""" - -from __future__ import annotations - -import warnings -from typing import Callable, Any -import numpy as np - -from qiskit.algorithms.optimizers import Minimizer, Optimizer -from qiskit.circuit import QuantumCircuit -from qiskit.circuit.library.n_local.qaoa_ansatz import QAOAAnsatz -from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseSampler -from qiskit.utils.validation import validate_min - -from .sampling_vqe import SamplingVQE - - -class QAOA(SamplingVQE): - r""" - The Quantum Approximate Optimization Algorithm (QAOA). - - QAOA is a well-known algorithm for finding approximate solutions to combinatorial-optimization - problems [1]. - - The QAOA implementation directly extends :class:`.SamplingVQE` and inherits its optimization - structure. However, unlike VQE, which can be configured with arbitrary ansatzes, QAOA uses its - own fine-tuned ansatz, which comprises :math:`p` parameterized global :math:`x` rotations and - :math:`p` different parameterizations of the problem hamiltonian. QAOA is thus principally - configured by the single integer parameter, ``reps``, which dictates the depth of the ansatz, - and thus affects the approximation quality. - - An optional array of :math:`2p` parameter values, as the :attr:`initial_point`, may be provided - as the starting :math:`\beta` and :math:`\gamma` parameters for the QAOA ansatz [1]. - - An operator or a parameterized quantum circuit may optionally also be provided as a custom - :attr:`mixer` Hamiltonian. This allows in the case of quantum annealing [2] and QAOA [3], to run - constrained optimization problems where the mixer constrains the evolution to a feasible - subspace of the full Hilbert space. - - The following attributes can be set via the initializer but can also be read and updated once - the QAOA object has been constructed. - - Attributes: - sampler (BaseSampler): The sampler primitive to sample the circuits. - optimizer (Optimizer | Minimizer): A classical optimizer to find the minimum energy. This - can either be a Qiskit :class:`.Optimizer` or a callable implementing the - :class:`.Minimizer` protocol. - reps (int): The integer parameter :math:`p`. Has a minimum valid value of 1. - initial_state: An optional initial state to prepend the QAOA circuit with. - mixer (QuantumCircuit | BaseOperator | PauliSumOp): The mixer Hamiltonian to evolve with or - a custom quantum circuit. Allows support of optimizations in constrained subspaces [2, - 3] as well as warm-starting the optimization [4]. - aggregation (float | Callable[[list[float]], float] | None): A float or callable to specify - how the objective function evaluated on the basis states should be aggregated. If a - float, this specifies the :math:`\alpha \in [0,1]` parameter for a CVaR expectation - value. - callback (Callable[[int, np.ndarray, float, dict[str, Any]], None] | None): A callback - that can access the intermediate data at each optimization step. These data are: the - evaluation count, the optimizer parameters for the ansatz, the evaluated value, the - the metadata dictionary, and the best measurement. - - References: - [1]: Farhi, E., Goldstone, J., Gutmann, S., "A Quantum Approximate Optimization Algorithm" - `arXiv:1411.4028 `__ - [2]: Hen, I., Spedalieri, F. M., "Quantum Annealing for Constrained Optimization" - `PhysRevApplied.5.034007 `__ - [3]: Hadfield, S. et al, "From the Quantum Approximate Optimization Algorithm to a Quantum - Alternating Operator Ansatz" `arXiv:1709.03489 `__ - [4]: Egger, D. J., Marecek, J., Woerner, S., "Warm-starting quantum optimization" - `arXiv: 2009.10095 `__ - """ - - def __init__( - self, - sampler: BaseSampler, - optimizer: Optimizer | Minimizer, - *, - reps: int = 1, - initial_state: QuantumCircuit | None = None, - mixer: QuantumCircuit | BaseOperator | PauliSumOp = None, - initial_point: np.ndarray | None = None, - aggregation: float | Callable[[list[float]], float] | None = None, - callback: Callable[[int, np.ndarray, float, dict[str, Any]], None] | None = None, - ) -> None: - r""" - Args: - sampler: The sampler primitive to sample the circuits. - optimizer: A classical optimizer to find the minimum energy. This can either be a - Qiskit :class:`.Optimizer` or a callable implementing the :class:`.Minimizer` - protocol. - reps: The integer parameter :math:`p`. Has a minimum valid value of 1. - initial_state: An optional initial state to prepend the QAOA circuit with. - mixer: The mixer Hamiltonian to evolve with or a custom quantum circuit. Allows support - of optimizations in constrained subspaces [2, 3] as well as warm-starting the - optimization [4]. - initial_point: An optional initial point (i.e. initial parameter values) for the - optimizer. The length of the initial point must match the number of :attr:`ansatz` - parameters. If ``None``, a random point will be generated within certain parameter - bounds. ``QAOA`` will look to the ansatz for these bounds. If the ansatz does not - specify bounds, bounds of :math:`-2\pi`, :math:`2\pi` will be used. - aggregation: A float or callable to specify how the objective function evaluated on the - basis states should be aggregated. If a float, this specifies the :math:`\alpha \in - [0,1]` parameter for a CVaR expectation value. - callback: A callback that can access the intermediate data at each optimization step. - These data are: the evaluation count, the optimizer parameters for the ansatz, the - evaluated value, the metadata dictionary. - """ - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - validate_min("reps", reps, 1) - - self.reps = reps - self.mixer = mixer - self.initial_state = initial_state - self._cost_operator = None - - super().__init__( - sampler=sampler, - ansatz=None, - optimizer=optimizer, - initial_point=initial_point, - aggregation=aggregation, - callback=callback, - ) - - def _check_operator_ansatz(self, operator: BaseOperator | PauliSumOp): - # Recreates a circuit based on operator parameter. - self.ansatz = QAOAAnsatz( - operator, self.reps, initial_state=self.initial_state, mixer_operator=self.mixer - ).decompose() # TODO remove decompose once #6674 is fixed diff --git a/qiskit/algorithms/minimum_eigensolvers/sampling_mes.py b/qiskit/algorithms/minimum_eigensolvers/sampling_mes.py deleted file mode 100644 index e193f53ce15c..000000000000 --- a/qiskit/algorithms/minimum_eigensolvers/sampling_mes.py +++ /dev/null @@ -1,138 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Sampling Minimum Eigensolver interface.""" - -from __future__ import annotations -from abc import ABC, abstractmethod -from collections.abc import Mapping -from typing import Any - -from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.opflow import PauliSumOp -from qiskit.result import QuasiDistribution -from ..algorithm_result import AlgorithmResult -from ..list_or_dict import ListOrDict - - -class SamplingMinimumEigensolver(ABC): - """The Sampling Minimum Eigensolver Interface.""" - - @abstractmethod - def compute_minimum_eigenvalue( - self, - operator: BaseOperator | PauliSumOp, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - ) -> "SamplingMinimumEigensolverResult": - """Compute the minimum eigenvalue of a diagonal operator. - - Args: - operator: Diagonal qubit operator. - aux_operators: Optional list of auxiliary operators to be evaluated with the - final state. - - Returns: - A :class:`~.SamplingMinimumEigensolverResult` containing the optimization result. - """ - pass - - @classmethod - def supports_aux_operators(cls) -> bool: - """Whether computing the expectation value of auxiliary operators is supported. - - If the minimum eigensolver computes an eigenstate of the main operator then it - can compute the expectation value of the aux_operators for that state. Otherwise - they will be ignored. - - Returns: - True if aux_operator expectations can be evaluated, False otherwise - """ - return False - - -class SamplingMinimumEigensolverResult(AlgorithmResult): - """Sampling Minimum Eigensolver Result. - - In contrast to the result of a :class:`~.MinimumEigenSolver`, this result also contains - the best measurement of the overall optimization and the samples of the final state. - """ - - def __init__(self) -> None: - super().__init__() - self._eigenvalue: complex | None = None - self._eigenstate: QuasiDistribution | None = None - self._aux_operator_values: ListOrDict[tuple[complex, dict[str, Any]]] | None = None - self._best_measurement: Mapping[str, Any] | None = None - - @property - def eigenvalue(self) -> complex | None: - """Return the approximation to the eigenvalue.""" - return self._eigenvalue - - @eigenvalue.setter - def eigenvalue(self, value: complex | None) -> None: - """Set the approximation to the eigenvalue.""" - self._eigenvalue = value - - @property - def eigenstate(self) -> QuasiDistribution | None: - """Return the quasi-distribution sampled from the final state. - - The ansatz is sampled when parameterized with the optimal parameters that where obtained - computing the minimum eigenvalue. The keys represent a measured classical value and the - value is a float for the quasi-probability of that result. - """ - return self._eigenstate - - @eigenstate.setter - def eigenstate(self, value: QuasiDistribution | None) -> None: - """Set the quasi-distribution sampled from the final state.""" - self._eigenstate = value - - @property - def aux_operators_evaluated(self) -> ListOrDict[tuple[complex, dict[str, Any]]] | None: - """Return aux operator expectation values and metadata. - - These are formatted as (mean, metadata). - """ - return self._aux_operator_values - - @aux_operators_evaluated.setter - def aux_operators_evaluated( - self, value: ListOrDict[tuple[complex, dict[str, Any]]] | None - ) -> None: - self._aux_operator_values = value - - @property - def best_measurement(self) -> Mapping[str, Any] | None: - """Return the best measurement over the entire optimization. - - Possesses keys: ``state``, ``bitstring``, ``value``, ``probability``. - """ - return self._best_measurement - - @best_measurement.setter - def best_measurement(self, value: Mapping[str, Any]) -> None: - """Set the best measurement over the entire optimization.""" - self._best_measurement = value - - def __str__(self) -> str: - """Return a string representation of the result.""" - disp = ( - "SamplingMinimumEigensolverResult:\n" - + f"\tEigenvalue: {self.eigenvalue}\n" - + f"\tBest measurement\n: {self.best_measurement}\n" - ) - if self.aux_operators_evaluated is not None: - disp += f"\n\tAuxiliary operator values: {self.aux_operators_evaluated}\n" - - return disp diff --git a/qiskit/algorithms/minimum_eigensolvers/sampling_vqe.py b/qiskit/algorithms/minimum_eigensolvers/sampling_vqe.py deleted file mode 100644 index 2fb60355b2a5..000000000000 --- a/qiskit/algorithms/minimum_eigensolvers/sampling_vqe.py +++ /dev/null @@ -1,382 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Variational Quantum Eigensolver algorithm, optimized for diagonal Hamiltonians.""" - -from __future__ import annotations - -from collections.abc import Callable, Sequence -import logging -from time import time -from typing import Any - -import numpy as np - -from qiskit.circuit import QuantumCircuit -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseSampler -from qiskit.result import QuasiDistribution -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..exceptions import AlgorithmError -from ..list_or_dict import ListOrDict -from ..optimizers import Minimizer, Optimizer, OptimizerResult -from ..variational_algorithm import VariationalAlgorithm, VariationalResult -from .diagonal_estimator import _DiagonalEstimator -from .sampling_mes import ( - SamplingMinimumEigensolver, - SamplingMinimumEigensolverResult, -) -from ..observables_evaluator import estimate_observables -from ..utils import validate_initial_point, validate_bounds - -# private function as we expect this to be updated in the next released -from ..utils.set_batching import _set_default_batchsize - - -logger = logging.getLogger(__name__) - - -class SamplingVQE(VariationalAlgorithm, SamplingMinimumEigensolver): - r"""The Variational Quantum Eigensolver algorithm, optimized for diagonal Hamiltonians. - - VQE is a hybrid quantum-classical algorithm that uses a variational technique to find the - minimum eigenvalue of a given diagonal Hamiltonian operator :math:`H_{\text{diag}}`. - - In contrast to the :class:`~qiskit.algorithms.minimum_eigensolvers.VQE` class, the - ``SamplingVQE`` algorithm is executed using a :attr:`sampler` primitive. - - An instance of ``SamplingVQE`` also requires an :attr:`ansatz`, a parameterized - :class:`.QuantumCircuit`, to prepare the trial state :math:`|\psi(\vec\theta)\rangle`. It also - needs a classical :attr:`optimizer` which varies the circuit parameters :math:`\vec\theta` to - minimize the objective function, which depends on the chosen :attr:`aggregation`. - - The optimizer can either be one of Qiskit's optimizers, such as - :class:`~qiskit.algorithms.optimizers.SPSA` or a callable with the following signature: - - .. code-block:: python - - from qiskit.algorithms.optimizers import OptimizerResult - - def my_minimizer(fun, x0, jac=None, bounds=None) -> OptimizerResult: - # Note that the callable *must* have these argument names! - # Args: - # fun (callable): the function to minimize - # x0 (np.ndarray): the initial point for the optimization - # jac (callable, optional): the gradient of the objective function - # bounds (list, optional): a list of tuples specifying the parameter bounds - - result = OptimizerResult() - result.x = # optimal parameters - result.fun = # optimal function value - return result - - The above signature also allows one to use any SciPy minimizer, for instance as - - .. code-block:: python - - from functools import partial - from scipy.optimize import minimize - - optimizer = partial(minimize, method="L-BFGS-B") - - The following attributes can be set via the initializer but can also be read and updated once - the ``SamplingVQE`` object has been constructed. - - Attributes: - sampler (BaseSampler): The sampler primitive to sample the circuits. - ansatz (QuantumCircuit): A parameterized quantum circuit to prepare the trial state. - optimizer (Optimizer | Minimizer): A classical optimizer to find the minimum energy. This - can either be a Qiskit :class:`.Optimizer` or a callable implementing the - :class:`.Minimizer` protocol. - aggregation (float | Callable[[list[tuple[float, complex]], float] | None): - A float or callable to specify how the objective function evaluated on the basis states - should be aggregated. If a float, this specifies the :math:`\alpha \in [0,1]` parameter - for a CVaR expectation value [1]. If a callable, it takes a list of basis state - measurements specified as ``[(probability, objective_value)]`` and return an objective - value as float. If None, all an ordinary expectation value is calculated. - callback (Callable[[int, np.ndarray, float, dict[str, Any]], None] | None): A callback that - can access the intermediate data at each optimization step. These data are: the - evaluation count, the optimizer parameters for the ansatz, the evaluated value, and the - metadata dictionary. - - References: - [1]: Barkoutsos, P. K., Nannicini, G., Robert, A., Tavernelli, I., and Woerner, S., - "Improving Variational Quantum Optimization using CVaR" - `arXiv:1907.04769 `_ - """ - - def __init__( - self, - sampler: BaseSampler, - ansatz: QuantumCircuit, - optimizer: Optimizer | Minimizer, - *, - initial_point: Sequence[float] | None = None, - aggregation: float | Callable[[list[float]], float] | None = None, - callback: Callable[[int, np.ndarray, float, dict[str, Any]], None] | None = None, - ) -> None: - r""" - Args: - sampler: The sampler primitive to sample the circuits. - ansatz: A parameterized quantum circuit to prepare the trial state. - optimizer: A classical optimizer to find the minimum energy. This can either be a Qiskit - :class:`.Optimizer` or a callable implementing the :class:`.Minimizer` protocol. - initial_point: An optional initial point (i.e. initial parameter values) for the - optimizer. The length of the initial point must match the number of :attr:`ansatz` - parameters. If ``None``, a random point will be generated within certain parameter - bounds. ``SamplingVQE`` will look to the ansatz for these bounds. If the ansatz does - not specify bounds, bounds of :math:`-2\pi`, :math:`2\pi` will be used. - aggregation: A float or callable to specify how the objective function evaluated on the - basis states should be aggregated. - callback: A callback that can access the intermediate data at each optimization step. - These data are: the evaluation count, the optimizer parameters for the ansatz, the - estimated value, and the metadata dictionary. - """ - super().__init__() - - self.sampler = sampler - self.ansatz = ansatz - self.optimizer = optimizer - self.aggregation = aggregation - self.callback = callback - - # this has to go via getters and setters due to the VariationalAlgorithm interface - self._initial_point = initial_point - - @property - def initial_point(self) -> Sequence[float] | None: - """Return the initial point.""" - return self._initial_point - - @initial_point.setter - def initial_point(self, value: Sequence[float] | None) -> None: - """Set the initial point.""" - self._initial_point = value - - def _check_operator_ansatz(self, operator: BaseOperator | PauliSumOp): - """Check that the number of qubits of operator and ansatz match and that the ansatz is - parameterized. - """ - if operator.num_qubits != self.ansatz.num_qubits: - try: - logger.info( - "Trying to resize ansatz to match operator on %s qubits.", operator.num_qubits - ) - self.ansatz.num_qubits = operator.num_qubits - except AttributeError as error: - raise AlgorithmError( - "The number of qubits of the ansatz does not match the " - "operator, and the ansatz does not allow setting the " - "number of qubits using `num_qubits`." - ) from error - - if self.ansatz.num_parameters == 0: - raise AlgorithmError("The ansatz must be parameterized, but has no free parameters.") - - @classmethod - def supports_aux_operators(cls) -> bool: - return True - - def compute_minimum_eigenvalue( - self, - operator: BaseOperator | PauliSumOp, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - ) -> SamplingMinimumEigensolverResult: - # check that the number of qubits of operator and ansatz match, and resize if possible - self._check_operator_ansatz(operator) - - if len(self.ansatz.clbits) > 0: - self.ansatz.remove_final_measurements() - self.ansatz.measure_all() - - initial_point = validate_initial_point(self.initial_point, self.ansatz) - - bounds = validate_bounds(self.ansatz) - - evaluate_energy, best_measurement = self._get_evaluate_energy( - operator, self.ansatz, return_best_measurement=True - ) - - start_time = time() - - if callable(self.optimizer): - optimizer_result = self.optimizer(fun=evaluate_energy, x0=initial_point, bounds=bounds) - else: - # we always want to submit as many estimations per job as possible for minimal - # overhead on the hardware - was_updated = _set_default_batchsize(self.optimizer) - - optimizer_result = self.optimizer.minimize( - fun=evaluate_energy, x0=initial_point, bounds=bounds - ) - - # reset to original value - if was_updated: - self.optimizer.set_max_evals_grouped(None) - - optimizer_time = time() - start_time - - logger.info( - "Optimization complete in %s seconds.\nFound opt_params %s.", - optimizer_time, - optimizer_result.x, - ) - - final_state = self.sampler.run([self.ansatz], [optimizer_result.x]).result().quasi_dists[0] - - if aux_operators is not None: - aux_operators_evaluated = estimate_observables( - _DiagonalEstimator(sampler=self.sampler), - self.ansatz, - aux_operators, - optimizer_result.x, - ) - else: - aux_operators_evaluated = None - - return self._build_sampling_vqe_result( - self.ansatz.copy(), - optimizer_result, - aux_operators_evaluated, - best_measurement, - final_state, - optimizer_time, - ) - - def _get_evaluate_energy( - self, - operator: BaseOperator | PauliSumOp, - ansatz: QuantumCircuit, - return_best_measurement: bool = False, - ) -> Callable[[np.ndarray], np.ndarray | float] | tuple[ - Callable[[np.ndarray], np.ndarray | float], dict[str, Any] - ]: - """Returns a function handle to evaluate the energy at given parameters. - - This is the objective function to be passed to the optimizer that is used for evaluation. - - Args: - operator: The operator whose energy to evaluate. - ansatz: The ansatz preparing the quantum state. - return_best_measurement: If True, a handle to a dictionary containing the best - measurement evaluated with the cost function. - - Returns: - A tuple of a callable evaluating the energy and (optionally) a dictionary containing the - best measurement of the energy evaluation. - - Raises: - AlgorithmError: If the circuit is not parameterized (i.e. has 0 free parameters). - - """ - num_parameters = ansatz.num_parameters - if num_parameters == 0: - raise AlgorithmError("The ansatz must be parameterized, but has 0 free parameters.") - - # avoid creating an instance variable to remain stateless regarding results - eval_count = 0 - - best_measurement = {"best": None} - - def store_best_measurement(best): - for best_i in best: - if best_measurement["best"] is None or _compare_measurements( - best_i, best_measurement["best"] - ): - best_measurement["best"] = best_i - - estimator = _DiagonalEstimator( - sampler=self.sampler, callback=store_best_measurement, aggregation=self.aggregation - ) - - def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float: - nonlocal eval_count - # handle broadcasting: ensure parameters is of shape [array, array, ...] - parameters = np.reshape(parameters, (-1, num_parameters)).tolist() - batch_size = len(parameters) - - estimator_result = estimator.run( - batch_size * [ansatz], batch_size * [operator], parameters - ).result() - values = estimator_result.values - - if self.callback is not None: - metadata = estimator_result.metadata - for params, value, meta in zip(parameters, values, metadata): - eval_count += 1 - self.callback(eval_count, params, value, meta) - - result = values if len(values) > 1 else values[0] - return np.real(result) - - if return_best_measurement: - return evaluate_energy, best_measurement - - return evaluate_energy - - def _build_sampling_vqe_result( - self, - ansatz: QuantumCircuit, - optimizer_result: OptimizerResult, - aux_operators_evaluated: ListOrDict[tuple[complex, tuple[complex, int]]], - best_measurement: dict[str, Any], - final_state: QuasiDistribution, - optimizer_time: float, - ) -> SamplingVQEResult: - result = SamplingVQEResult() - result.eigenvalue = optimizer_result.fun - result.cost_function_evals = optimizer_result.nfev - result.optimal_point = optimizer_result.x - result.optimal_parameters = dict(zip(self.ansatz.parameters, optimizer_result.x)) - result.optimal_value = optimizer_result.fun - result.optimizer_time = optimizer_time - result.aux_operators_evaluated = aux_operators_evaluated - result.optimizer_result = optimizer_result - result.best_measurement = best_measurement["best"] - result.eigenstate = final_state - result.optimal_circuit = ansatz - return result - - -class SamplingVQEResult(VariationalResult, SamplingMinimumEigensolverResult): - """VQE Result.""" - - def __init__(self) -> None: - super().__init__() - self._cost_function_evals: int | None = None - - @property - def cost_function_evals(self) -> int | None: - """Returns number of cost optimizer evaluations""" - return self._cost_function_evals - - @cost_function_evals.setter - def cost_function_evals(self, value: int) -> None: - """Sets number of cost function evaluations""" - self._cost_function_evals = value - - -def _compare_measurements(candidate, current_best): - """Compare two best measurements. Returns True if the candidate is better than current value. - - This compares the following two criteria, in this precedence: - - 1. The smaller objective value is better - 2. The higher probability for the objective value is better - - """ - if candidate["value"] < current_best["value"]: - return True - elif candidate["value"] == current_best["value"]: - return candidate["probability"] > current_best["probability"] - return False diff --git a/qiskit/algorithms/minimum_eigensolvers/vqe.py b/qiskit/algorithms/minimum_eigensolvers/vqe.py deleted file mode 100644 index f26d3687971c..000000000000 --- a/qiskit/algorithms/minimum_eigensolvers/vqe.py +++ /dev/null @@ -1,356 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The variational quantum eigensolver algorithm.""" - -from __future__ import annotations - -import logging -from time import time -from collections.abc import Callable, Sequence -from typing import Any - -import numpy as np - -from qiskit.algorithms.gradients import BaseEstimatorGradient -from qiskit.circuit import QuantumCircuit -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseEstimator -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..exceptions import AlgorithmError -from ..list_or_dict import ListOrDict -from ..optimizers import Optimizer, Minimizer, OptimizerResult -from ..variational_algorithm import VariationalAlgorithm, VariationalResult -from .minimum_eigensolver import MinimumEigensolver, MinimumEigensolverResult -from ..observables_evaluator import estimate_observables -from ..utils import validate_initial_point, validate_bounds - -# private function as we expect this to be updated in the next released -from ..utils.set_batching import _set_default_batchsize - -logger = logging.getLogger(__name__) - - -class VQE(VariationalAlgorithm, MinimumEigensolver): - r"""The variational quantum eigensolver (VQE) algorithm. - - VQE is a hybrid quantum-classical algorithm that uses a variational technique to find the - minimum eigenvalue of a given Hamiltonian operator :math:`H`. - - The ``VQE`` algorithm is executed using an :attr:`estimator` primitive, which computes - expectation values of operators (observables). - - An instance of ``VQE`` also requires an :attr:`ansatz`, a parameterized - :class:`.QuantumCircuit`, to prepare the trial state :math:`|\psi(\vec\theta)\rangle`. It also - needs a classical :attr:`optimizer` which varies the circuit parameters :math:`\vec\theta` such - that the expectation value of the operator on the corresponding state approaches a minimum, - - .. math:: - - \min_{\vec\theta} \langle\psi(\vec\theta)|H|\psi(\vec\theta)\rangle. - - The :attr:`estimator` is used to compute this expectation value for every optimization step. - - The optimizer can either be one of Qiskit's optimizers, such as - :class:`~qiskit.algorithms.optimizers.SPSA` or a callable with the following signature: - - .. code-block:: python - - from qiskit.algorithms.optimizers import OptimizerResult - - def my_minimizer(fun, x0, jac=None, bounds=None) -> OptimizerResult: - # Note that the callable *must* have these argument names! - # Args: - # fun (callable): the function to minimize - # x0 (np.ndarray): the initial point for the optimization - # jac (callable, optional): the gradient of the objective function - # bounds (list, optional): a list of tuples specifying the parameter bounds - - result = OptimizerResult() - result.x = # optimal parameters - result.fun = # optimal function value - return result - - The above signature also allows one to use any SciPy minimizer, for instance as - - .. code-block:: python - - from functools import partial - from scipy.optimize import minimize - - optimizer = partial(minimize, method="L-BFGS-B") - - The following attributes can be set via the initializer but can also be read and updated once - the VQE object has been constructed. - - Attributes: - estimator (BaseEstimator): The estimator primitive to compute the expectation value of the - Hamiltonian operator. - ansatz (QuantumCircuit): A parameterized quantum circuit to prepare the trial state. - optimizer (Optimizer | Minimizer): A classical optimizer to find the minimum energy. This - can either be a Qiskit :class:`.Optimizer` or a callable implementing the - :class:`.Minimizer` protocol. - gradient (BaseEstimatorGradient | None): An optional estimator gradient to be used with the - optimizer. - callback (Callable[[int, np.ndarray, float, dict[str, Any]], None] | None): A callback that - can access the intermediate data at each optimization step. These data are: the - evaluation count, the optimizer parameters for the ansatz, the evaluated mean, and the - metadata dictionary. - - References: - [1]: Peruzzo, A., et al, "A variational eigenvalue solver on a quantum processor" - `arXiv:1304.3061 `__ - """ - - def __init__( - self, - estimator: BaseEstimator, - ansatz: QuantumCircuit, - optimizer: Optimizer | Minimizer, - *, - gradient: BaseEstimatorGradient | None = None, - initial_point: Sequence[float] | None = None, - callback: Callable[[int, np.ndarray, float, dict[str, Any]], None] | None = None, - ) -> None: - r""" - Args: - estimator: The estimator primitive to compute the expectation value of the - Hamiltonian operator. - ansatz: A parameterized quantum circuit to prepare the trial state. - optimizer: A classical optimizer to find the minimum energy. This can either be a - Qiskit :class:`.Optimizer` or a callable implementing the :class:`.Minimizer` - protocol. - gradient: An optional estimator gradient to be used with the optimizer. - initial_point: An optional initial point (i.e. initial parameter values) for the - optimizer. The length of the initial point must match the number of :attr:`ansatz` - parameters. If ``None``, a random point will be generated within certain parameter - bounds. ``VQE`` will look to the ansatz for these bounds. If the ansatz does not - specify bounds, bounds of :math:`-2\pi`, :math:`2\pi` will be used. - callback: A callback that can access the intermediate data at each optimization step. - These data are: the evaluation count, the optimizer parameters for the ansatz, the - estimated value, and the metadata dictionary. - """ - super().__init__() - - self.estimator = estimator - self.ansatz = ansatz - self.optimizer = optimizer - self.gradient = gradient - # this has to go via getters and setters due to the VariationalAlgorithm interface - self.initial_point = initial_point - self.callback = callback - - @property - def initial_point(self) -> Sequence[float] | None: - return self._initial_point - - @initial_point.setter - def initial_point(self, value: Sequence[float] | None) -> None: - self._initial_point = value - - def compute_minimum_eigenvalue( - self, - operator: BaseOperator | PauliSumOp, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - ) -> VQEResult: - self._check_operator_ansatz(operator) - - initial_point = validate_initial_point(self.initial_point, self.ansatz) - - bounds = validate_bounds(self.ansatz) - - start_time = time() - - evaluate_energy = self._get_evaluate_energy(self.ansatz, operator) - - if self.gradient is not None: - evaluate_gradient = self._get_evaluate_gradient(self.ansatz, operator) - else: - evaluate_gradient = None - - # perform optimization - if callable(self.optimizer): - optimizer_result = self.optimizer( - fun=evaluate_energy, x0=initial_point, jac=evaluate_gradient, bounds=bounds - ) - else: - # we always want to submit as many estimations per job as possible for minimal - # overhead on the hardware - was_updated = _set_default_batchsize(self.optimizer) - - optimizer_result = self.optimizer.minimize( - fun=evaluate_energy, x0=initial_point, jac=evaluate_gradient, bounds=bounds - ) - - # reset to original value - if was_updated: - self.optimizer.set_max_evals_grouped(None) - - optimizer_time = time() - start_time - - logger.info( - "Optimization complete in %s seconds.\nFound optimal point %s", - optimizer_time, - optimizer_result.x, - ) - - if aux_operators is not None: - aux_operators_evaluated = estimate_observables( - self.estimator, self.ansatz, aux_operators, optimizer_result.x - ) - else: - aux_operators_evaluated = None - - return self._build_vqe_result( - self.ansatz, optimizer_result, aux_operators_evaluated, optimizer_time - ) - - @classmethod - def supports_aux_operators(cls) -> bool: - return True - - def _get_evaluate_energy( - self, - ansatz: QuantumCircuit, - operator: BaseOperator | PauliSumOp, - ) -> Callable[[np.ndarray], np.ndarray | float]: - """Returns a function handle to evaluate the energy at given parameters for the ansatz. - This is the objective function to be passed to the optimizer that is used for evaluation. - - Args: - ansatz: The ansatz preparing the quantum state. - operator: The operator whose energy to evaluate. - - Returns: - A callable that computes and returns the energy of the hamiltonian of each parameter. - - Raises: - AlgorithmError: If the primitive job to evaluate the energy fails. - """ - num_parameters = ansatz.num_parameters - - # avoid creating an instance variable to remain stateless regarding results - eval_count = 0 - - def evaluate_energy(parameters: np.ndarray) -> np.ndarray | float: - nonlocal eval_count - - # handle broadcasting: ensure parameters is of shape [array, array, ...] - parameters = np.reshape(parameters, (-1, num_parameters)).tolist() - batch_size = len(parameters) - - try: - job = self.estimator.run(batch_size * [ansatz], batch_size * [operator], parameters) - estimator_result = job.result() - except Exception as exc: - raise AlgorithmError("The primitive job to evaluate the energy failed!") from exc - - values = estimator_result.values - - if self.callback is not None: - metadata = estimator_result.metadata - for params, value, meta in zip(parameters, values, metadata): - eval_count += 1 - self.callback(eval_count, params, value, meta) - - energy = values[0] if len(values) == 1 else values - - return energy - - return evaluate_energy - - def _get_evaluate_gradient( - self, - ansatz: QuantumCircuit, - operator: BaseOperator | PauliSumOp, - ) -> Callable[[np.ndarray], np.ndarray]: - """Get a function handle to evaluate the gradient at given parameters for the ansatz. - - Args: - ansatz: The ansatz preparing the quantum state. - operator: The operator whose energy to evaluate. - - Returns: - A function handle to evaluate the gradient at given parameters for the ansatz. - - Raises: - AlgorithmError: If the primitive job to evaluate the gradient fails. - """ - - def evaluate_gradient(parameters: np.ndarray) -> np.ndarray: - # broadcasting not required for the estimator gradients - try: - job = self.gradient.run([ansatz], [operator], [parameters]) - gradients = job.result().gradients - except Exception as exc: - raise AlgorithmError("The primitive job to evaluate the gradient failed!") from exc - - return gradients[0] - - return evaluate_gradient - - def _check_operator_ansatz(self, operator: BaseOperator | PauliSumOp): - """Check that the number of qubits of operator and ansatz match and that the ansatz is - parameterized. - """ - if operator.num_qubits != self.ansatz.num_qubits: - try: - logger.info( - "Trying to resize ansatz to match operator on %s qubits.", operator.num_qubits - ) - self.ansatz.num_qubits = operator.num_qubits - except AttributeError as error: - raise AlgorithmError( - "The number of qubits of the ansatz does not match the " - "operator, and the ansatz does not allow setting the " - "number of qubits using `num_qubits`." - ) from error - - if self.ansatz.num_parameters == 0: - raise AlgorithmError("The ansatz must be parameterized, but has no free parameters.") - - def _build_vqe_result( - self, - ansatz: QuantumCircuit, - optimizer_result: OptimizerResult, - aux_operators_evaluated: ListOrDict[tuple[complex, tuple[complex, int]]], - optimizer_time: float, - ) -> VQEResult: - result = VQEResult() - result.optimal_circuit = ansatz.copy() - result.eigenvalue = optimizer_result.fun - result.cost_function_evals = optimizer_result.nfev - result.optimal_point = optimizer_result.x - result.optimal_parameters = dict(zip(self.ansatz.parameters, optimizer_result.x)) - result.optimal_value = optimizer_result.fun - result.optimizer_time = optimizer_time - result.aux_operators_evaluated = aux_operators_evaluated - result.optimizer_result = optimizer_result - return result - - -class VQEResult(VariationalResult, MinimumEigensolverResult): - """Variational quantum eigensolver result.""" - - def __init__(self) -> None: - super().__init__() - self._cost_function_evals: int | None = None - - @property - def cost_function_evals(self) -> int | None: - """The number of cost optimizer evaluations.""" - return self._cost_function_evals - - @cost_function_evals.setter - def cost_function_evals(self, value: int) -> None: - self._cost_function_evals = value diff --git a/qiskit/algorithms/observables_evaluator.py b/qiskit/algorithms/observables_evaluator.py deleted file mode 100644 index 6d40239e229e..000000000000 --- a/qiskit/algorithms/observables_evaluator.py +++ /dev/null @@ -1,126 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Evaluator of observables for algorithms.""" - -from __future__ import annotations -from collections.abc import Sequence -from typing import Any - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info import SparsePauliOp -from .exceptions import AlgorithmError -from .list_or_dict import ListOrDict -from ..primitives import BaseEstimator -from ..quantum_info.operators.base_operator import BaseOperator - - -def estimate_observables( - estimator: BaseEstimator, - quantum_state: QuantumCircuit, - observables: ListOrDict[BaseOperator | PauliSumOp], - parameter_values: Sequence[float] | None = None, - threshold: float = 1e-12, -) -> ListOrDict[tuple[complex, dict[str, Any]]]: - """ - Accepts a sequence of operators and calculates their expectation values - means - and metadata. They are calculated with respect to a quantum state provided. A user - can optionally provide a threshold value which filters mean values falling below the threshold. - - Args: - estimator: An estimator primitive used for calculations. - quantum_state: A (parameterized) quantum circuit preparing a quantum state that expectation - values are computed against. - observables: A list or a dictionary of operators whose expectation values are to be - calculated. - parameter_values: Optional list of parameters values to evaluate the quantum circuit on. - threshold: A threshold value that defines which mean values should be neglected (helpful for - ignoring numerical instabilities close to 0). - - Returns: - A list or a dictionary of tuples (mean, metadata). - - Raises: - AlgorithmError: If a primitive job is not successful. - """ - - if isinstance(observables, dict): - observables_list = list(observables.values()) - else: - observables_list = observables - - if len(observables_list) > 0: - observables_list = _handle_zero_ops(observables_list) - quantum_state = [quantum_state] * len(observables) - if parameter_values is not None: - parameter_values = [parameter_values] * len(observables) - try: - estimator_job = estimator.run(quantum_state, observables_list, parameter_values) - expectation_values = estimator_job.result().values - except Exception as exc: - raise AlgorithmError("The primitive job failed!") from exc - - metadata = estimator_job.result().metadata - # Discard values below threshold - observables_means = expectation_values * (np.abs(expectation_values) > threshold) - # zip means and metadata into tuples - observables_results = list(zip(observables_means, metadata)) - else: - observables_results = [] - - return _prepare_result(observables_results, observables) - - -def _handle_zero_ops( - observables_list: list[BaseOperator | PauliSumOp], -) -> list[BaseOperator | PauliSumOp]: - """Replaces all occurrence of operators equal to 0 in the list with an equivalent ``PauliSumOp`` - operator.""" - if observables_list: - zero_op = SparsePauliOp.from_list([("I" * observables_list[0].num_qubits, 0)]) - for ind, observable in enumerate(observables_list): - if observable == 0: - observables_list[ind] = zero_op - return observables_list - - -def _prepare_result( - observables_results: list[tuple[complex, dict]], - observables: ListOrDict[BaseOperator | PauliSumOp], -) -> ListOrDict[tuple[complex, dict[str, Any]]]: - """ - Prepares a list of tuples of eigenvalues and metadata tuples from - ``observables_results`` and ``observables``. - - Args: - observables_results: A list of tuples (mean, metadata). - observables: A list or a dictionary of operators whose expectation values are to be - calculated. - - Returns: - A list or a dictionary of tuples (mean, metadata). - """ - - if isinstance(observables, list): - # by construction, all None values will be overwritten - observables_eigenvalues: ListOrDict[tuple[complex, complex]] = [None] * len(observables) - key_value_iterator = enumerate(observables_results) - else: - observables_eigenvalues = {} - key_value_iterator = zip(observables.keys(), observables_results) - - for key, value in key_value_iterator: - observables_eigenvalues[key] = value - return observables_eigenvalues diff --git a/qiskit/algorithms/optimizers/__init__.py b/qiskit/algorithms/optimizers/__init__.py deleted file mode 100644 index 11bf73d1fcaf..000000000000 --- a/qiskit/algorithms/optimizers/__init__.py +++ /dev/null @@ -1,182 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Optimizers (:mod:`qiskit.algorithms.optimizers`) -===================================================== -It contains a variety of classical optimizers for use by quantum variational algorithms, -such as :class:`~qiskit.algorithms.VQE`. -Logically, these optimizers can be divided into two categories: - -`Local Optimizers`_ - Given an optimization problem, a **local optimizer** is a function - that attempts to find an optimal value within the neighboring set of a candidate solution. - -`Global Optimizers`_ - Given an optimization problem, a **global optimizer** is a function - that attempts to find an optimal value among all possible solutions. - -.. currentmodule:: qiskit.algorithms.optimizers - -Optimizer Base Class -==================== - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - OptimizerResult - OptimizerSupportLevel - Optimizer - Minimizer - -Steppable Optimizer Base Class -============================== - -.. autosummary:: - :toctree: ../stubs/ - - optimizer_utils - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - SteppableOptimizer - AskData - TellData - OptimizerState - - - -Local Optimizers -================ - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - ADAM - AQGD - CG - COBYLA - L_BFGS_B - GSLS - GradientDescent - GradientDescentState - NELDER_MEAD - NFT - P_BFGS - POWELL - SLSQP - SPSA - QNSPSA - TNC - SciPyOptimizer - UMDA - -Qiskit also provides the following optimizers, which are built-out using the optimizers from -the `scikit-quant` package. The `scikit-quant` package is not installed by default but must be -explicitly installed, if desired, by the user - the optimizers therein are provided under various -licenses so it has been made an optional install for the end user to choose whether to do so or -not. To install the `scikit-quant` dependent package you can use -`pip install scikit-quant`. - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - BOBYQA - IMFIL - SNOBFIT - -Global Optimizers -================= -The global optimizers here all use NLopt for their core function and can only be -used if their dependent NLopt package is manually installed. - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - CRS - DIRECT_L - DIRECT_L_RAND - ESCH - ISRES - -""" - -from .adam_amsgrad import ADAM -from .aqgd import AQGD -from .bobyqa import BOBYQA -from .cg import CG -from .cobyla import COBYLA -from .gsls import GSLS -from .gradient_descent import GradientDescent, GradientDescentState -from .imfil import IMFIL -from .l_bfgs_b import L_BFGS_B -from .nelder_mead import NELDER_MEAD -from .nft import NFT -from .nlopts.crs import CRS -from .nlopts.direct_l import DIRECT_L -from .nlopts.direct_l_rand import DIRECT_L_RAND -from .nlopts.esch import ESCH -from .nlopts.isres import ISRES -from .steppable_optimizer import SteppableOptimizer, AskData, TellData, OptimizerState -from .optimizer import Minimizer, Optimizer, OptimizerResult, OptimizerSupportLevel -from .p_bfgs import P_BFGS -from .powell import POWELL -from .qnspsa import QNSPSA -from .scipy_optimizer import SciPyOptimizer -from .slsqp import SLSQP -from .snobfit import SNOBFIT -from .spsa import SPSA -from .tnc import TNC -from .umda import UMDA - -__all__ = [ - "Optimizer", - "OptimizerSupportLevel", - "SteppableOptimizer", - "AskData", - "TellData", - "OptimizerState", - "OptimizerResult", - "Minimizer", - "ADAM", - "AQGD", - "CG", - "COBYLA", - "GSLS", - "GradientDescent", - "GradientDescentState", - "L_BFGS_B", - "NELDER_MEAD", - "NFT", - "P_BFGS", - "POWELL", - "SciPyOptimizer", - "SLSQP", - "SPSA", - "QNSPSA", - "TNC", - "CRS", - "DIRECT_L", - "DIRECT_L_RAND", - "ESCH", - "ISRES", - "SNOBFIT", - "BOBYQA", - "IMFIL", - "UMDA", -] diff --git a/qiskit/algorithms/optimizers/adam_amsgrad.py b/qiskit/algorithms/optimizers/adam_amsgrad.py deleted file mode 100644 index 422aa17a5f01..000000000000 --- a/qiskit/algorithms/optimizers/adam_amsgrad.py +++ /dev/null @@ -1,270 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Adam and AMSGRAD optimizers.""" -from __future__ import annotations - -from collections.abc import Callable -from typing import Any -import os - -import csv -import numpy as np -from qiskit.utils.deprecation import deprecate_arg -from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT - -# pylint: disable=invalid-name - - -class ADAM(Optimizer): - """Adam and AMSGRAD optimizers. - - Adam [1] is a gradient-based optimization algorithm that is relies on adaptive estimates of - lower-order moments. The algorithm requires little memory and is invariant to diagonal - rescaling of the gradients. Furthermore, it is able to cope with non-stationary objective - functions and noisy and/or sparse gradients. - - AMSGRAD [2] (a variant of Adam) uses a 'long-term memory' of past gradients and, thereby, - improves convergence properties. - - References: - - [1]: Kingma, Diederik & Ba, Jimmy (2014), Adam: A Method for Stochastic Optimization. - `arXiv:1412.6980 `_ - - [2]: Sashank J. Reddi and Satyen Kale and Sanjiv Kumar (2018), - On the Convergence of Adam and Beyond. - `arXiv:1904.09237 `_ - - .. note:: - - This component has some function that is normally random. If you want to reproduce behavior - then you should set the random number generator seed in the algorithm_globals - (``qiskit.utils.algorithm_globals.random_seed = seed``). - - """ - - _OPTIONS = [ - "maxiter", - "tol", - "lr", - "beta_1", - "beta_2", - "noise_factor", - "eps", - "amsgrad", - "snapshot_dir", - ] - - def __init__( - self, - maxiter: int = 10000, - tol: float = 1e-6, - lr: float = 1e-3, - beta_1: float = 0.9, - beta_2: float = 0.99, - noise_factor: float = 1e-8, - eps: float = 1e-10, - amsgrad: bool = False, - snapshot_dir: str | None = None, - ) -> None: - """ - Args: - maxiter: Maximum number of iterations - tol: Tolerance for termination - lr: Value >= 0, Learning rate. - beta_1: Value in range 0 to 1, Generally close to 1. - beta_2: Value in range 0 to 1, Generally close to 1. - noise_factor: Value >= 0, Noise factor - eps : Value >=0, Epsilon to be used for finite differences if no analytic - gradient method is given. - amsgrad: True to use AMSGRAD, False if not - snapshot_dir: If not None save the optimizer's parameter - after every step to the given directory - """ - super().__init__() - for k, v in list(locals().items()): - if k in self._OPTIONS: - self._options[k] = v - self._maxiter = maxiter - self._snapshot_dir = snapshot_dir - self._tol = tol - self._lr = lr - self._beta_1 = beta_1 - self._beta_2 = beta_2 - self._noise_factor = noise_factor - self._eps = eps - self._amsgrad = amsgrad - - # runtime variables - self._t = 0 # time steps - self._m = np.zeros(1) - self._v = np.zeros(1) - if self._amsgrad: - self._v_eff = np.zeros(1) - - if self._snapshot_dir: - - with open(os.path.join(self._snapshot_dir, "adam_params.csv"), mode="w") as csv_file: - if self._amsgrad: - fieldnames = ["v", "v_eff", "m", "t"] - else: - fieldnames = ["v", "m", "t"] - writer = csv.DictWriter(csv_file, fieldnames=fieldnames) - writer.writeheader() - - @property - def settings(self) -> dict[str, Any]: - return { - "maxiter": self._maxiter, - "tol": self._tol, - "lr": self._lr, - "beta_1": self._beta_1, - "beta_2": self._beta_2, - "noise_factor": self._noise_factor, - "eps": self._eps, - "amsgrad": self._amsgrad, - "snapshot_dir": self._snapshot_dir, - } - - def get_support_level(self): - """Return support level dictionary""" - return { - "gradient": OptimizerSupportLevel.supported, - "bounds": OptimizerSupportLevel.ignored, - "initial_point": OptimizerSupportLevel.supported, - } - - def save_params(self, snapshot_dir: str) -> None: - """Save the current iteration parameters to a file called ``adam_params.csv``. - - Note: - - The current parameters are appended to the file, if it exists already. - The file is not overwritten. - - Args: - snapshot_dir: The directory to store the file in. - """ - if self._amsgrad: - with open(os.path.join(snapshot_dir, "adam_params.csv"), mode="a") as csv_file: - fieldnames = ["v", "v_eff", "m", "t"] - writer = csv.DictWriter(csv_file, fieldnames=fieldnames) - writer.writerow({"v": self._v, "v_eff": self._v_eff, "m": self._m, "t": self._t}) - else: - with open(os.path.join(snapshot_dir, "adam_params.csv"), mode="a") as csv_file: - fieldnames = ["v", "m", "t"] - writer = csv.DictWriter(csv_file, fieldnames=fieldnames) - writer.writerow({"v": self._v, "m": self._m, "t": self._t}) - - def load_params(self, load_dir: str) -> None: - """Load iteration parameters for a file called ``adam_params.csv``. - - Args: - load_dir: The directory containing ``adam_params.csv``. - """ - with open(os.path.join(load_dir, "adam_params.csv")) as csv_file: - if self._amsgrad: - fieldnames = ["v", "v_eff", "m", "t"] - else: - fieldnames = ["v", "m", "t"] - reader = csv.DictReader(csv_file, fieldnames=fieldnames) - for line in reader: - v = line["v"] - if self._amsgrad: - v_eff = line["v_eff"] - m = line["m"] - t = line["t"] - - v = v[1:-1] - self._v = np.fromstring(v, dtype=float, sep=" ") - if self._amsgrad: - v_eff = v_eff[1:-1] - self._v_eff = np.fromstring(v_eff, dtype=float, sep=" ") - m = m[1:-1] - self._m = np.fromstring(m, dtype=float, sep=" ") - t = t[1:-1] - self._t = np.fromstring(t, dtype=int, sep=" ") - - @deprecate_arg("objective_function", new_alias="fun", since="0.19.0") - @deprecate_arg("initial_point", new_alias="fun", since="0.19.0") - @deprecate_arg("gradient_function", new_alias="jac", since="0.19.0") - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - # pylint:disable=unused-argument - objective_function: Callable[[np.ndarray], float] | None = None, - initial_point: np.ndarray | None = None, - gradient_function: Callable[[np.ndarray], float] | None = None, - # ) -> Tuple[np.ndarray, float, int]: - ) -> OptimizerResult: # TODO find proper way to deprecate return type - """Minimize the scalar function. - - Args: - fun: The scalar function to minimize. - x0: The initial point for the minimization. - jac: The gradient of the scalar function ``fun``. - bounds: Bounds for the variables of ``fun``. This argument might be ignored if the - optimizer does not support bounds. - objective_function: DEPRECATED. A function handle to the objective function. - initial_point: DEPRECATED. The initial iteration point. - gradient_function: DEPRECATED. A function handle to the gradient of the objective - function. - - Returns: - The result of the optimization, containing e.g. the result as attribute ``x``. - """ - if jac is None: - jac = Optimizer.wrap_function(Optimizer.gradient_num_diff, (fun, self._eps)) - - derivative = jac(x0) - self._t = 0 - self._m = np.zeros(np.shape(derivative)) - self._v = np.zeros(np.shape(derivative)) - if self._amsgrad: - self._v_eff = np.zeros(np.shape(derivative)) - - params = params_new = x0 - while self._t < self._maxiter: - if self._t > 0: - derivative = jac(params) - self._t += 1 - self._m = self._beta_1 * self._m + (1 - self._beta_1) * derivative - self._v = self._beta_2 * self._v + (1 - self._beta_2) * derivative * derivative - lr_eff = self._lr * np.sqrt(1 - self._beta_2**self._t) / (1 - self._beta_1**self._t) - if not self._amsgrad: - params_new = params - lr_eff * self._m.flatten() / ( - np.sqrt(self._v.flatten()) + self._noise_factor - ) - else: - self._v_eff = np.maximum(self._v_eff, self._v) - params_new = params - lr_eff * self._m.flatten() / ( - np.sqrt(self._v_eff.flatten()) + self._noise_factor - ) - - if self._snapshot_dir: - self.save_params(self._snapshot_dir) - - # check termination - if np.linalg.norm(params - params_new) < self._tol: - break - - params = params_new - - result = OptimizerResult() - result.x = params_new - result.fun = fun(params_new) - result.nfev = self._t - return result diff --git a/qiskit/algorithms/optimizers/aqgd.py b/qiskit/algorithms/optimizers/aqgd.py deleted file mode 100644 index ad8ad42ae9f1..000000000000 --- a/qiskit/algorithms/optimizers/aqgd.py +++ /dev/null @@ -1,367 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Analytical Quantum Gradient Descent (AQGD) optimizer.""" - -from __future__ import annotations -import logging -from collections.abc import Callable -from typing import Any -import warnings - -import numpy as np -from qiskit.utils.validation import validate_range_exclusive_max -from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT -from ..exceptions import AlgorithmError - -logger = logging.getLogger(__name__) - - -class AQGD(Optimizer): - """Analytic Quantum Gradient Descent (AQGD) with Epochs optimizer. - Performs gradient descent optimization with a momentum term, analytic gradients, - and customized step length schedule for parameterized quantum gates, i.e. - Pauli Rotations. See, for example: - - * K. Mitarai, M. Negoro, M. Kitagawa, and K. Fujii. (2018). - Quantum circuit learning. Phys. Rev. A 98, 032309. - https://arxiv.org/abs/1803.00745 - - * Maria Schuld, Ville Bergholm, Christian Gogolin, Josh Izaac, Nathan Killoran. (2019). - Evaluating analytic gradients on quantum hardware. Phys. Rev. A 99, 032331. - https://arxiv.org/abs/1811.11184 - - for further details on analytic gradients of parameterized quantum gates. - - Gradients are computed "analytically" using the quantum circuit when evaluating - the objective function. - - """ - - _OPTIONS = ["maxiter", "eta", "tol", "disp", "momentum", "param_tol", "averaging"] - - def __init__( - self, - maxiter: int | list[int] = 1000, - eta: float | list[float] = 1.0, - tol: float = 1e-6, # this is tol - momentum: float | list[float] = 0.25, - param_tol: float = 1e-6, - averaging: int = 10, - ) -> None: - """ - Performs Analytical Quantum Gradient Descent (AQGD) with Epochs. - - Args: - maxiter: Maximum number of iterations (full gradient steps) - eta: The coefficient of the gradient update. Increasing this value - results in larger step sizes: param = previous_param - eta * deriv - tol: Tolerance for change in windowed average of objective values. - Convergence occurs when either objective tolerance is met OR parameter - tolerance is met. - momentum: Bias towards the previous gradient momentum in current - update. Must be within the bounds: [0,1) - param_tol: Tolerance for change in norm of parameters. - averaging: Length of window over which to average objective values for objective - convergence criterion - - Raises: - AlgorithmError: If the length of ``maxiter``, `momentum``, and ``eta`` is not the same. - """ - super().__init__() - if isinstance(maxiter, int): - maxiter = [maxiter] - if isinstance(eta, (int, float)): - eta = [eta] - if isinstance(momentum, (int, float)): - momentum = [momentum] - if len(maxiter) != len(eta) or len(maxiter) != len(momentum): - raise AlgorithmError( - "AQGD input parameter length mismatch. Parameters `maxiter`, " - "`eta`, and `momentum` must have the same length." - ) - for m in momentum: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - validate_range_exclusive_max("momentum", m, 0, 1) - - self._eta = eta - self._maxiter = maxiter - self._momenta_coeff = momentum - self._param_tol = param_tol - self._tol = tol - self._averaging = averaging - - # state - self._avg_objval: float | None = None - self._prev_param: np.ndarray | None = None - self._eval_count = 0 # function evaluations - self._prev_loss: list[float] = [] - self._prev_grad: list[list[float]] = [] - - def get_support_level(self) -> dict[str, OptimizerSupportLevel]: - """Support level dictionary - - Returns: - Dict[str, int]: gradient, bounds and initial point - support information that is ignored/required. - """ - return { - "gradient": OptimizerSupportLevel.ignored, - "bounds": OptimizerSupportLevel.ignored, - "initial_point": OptimizerSupportLevel.required, - } - - @property - def settings(self) -> dict[str, Any]: - return { - "maxiter": self._maxiter, - "eta": self._eta, - "momentum": self._momenta_coeff, - "param_tol": self._param_tol, - "tol": self._tol, - "averaging": self._averaging, - } - - def _compute_objective_fn_and_gradient( - self, params: np.ndarray | list[float], obj: Callable - ) -> tuple[float, np.ndarray]: - """ - Obtains the objective function value for params and the analytical quantum derivatives of - the objective function with respect to each parameter. Requires - 2*(number parameters) + 1 objective evaluations - - Args: - params: Current value of the parameters to evaluate the objective function - obj: Objective function of interest - - Returns: - Tuple containing the objective value and array of gradients for the given parameter set. - """ - num_params = len(params) - param_sets_to_eval = params + np.concatenate( - ( - np.zeros((1, num_params)), # copy of the parameters as is - np.eye(num_params) * np.pi / 2, # copy of the parameters with the positive shift - -np.eye(num_params) * np.pi / 2, - ), # copy of the parameters with the negative shift - axis=0, - ) - # Evaluate, - # reshaping to flatten, as expected by objective function - values = np.array(obj(param_sets_to_eval.reshape(-1))) - - # Update number of objective function evaluations - self._eval_count += 2 * num_params + 1 - - # return the objective function value - obj_value = values[0] - - # return the gradient values - gradient = 0.5 * (values[1 : num_params + 1] - values[1 + num_params :]) - return obj_value, gradient - - def _update( - self, - params: np.ndarray, - gradient: np.ndarray, - mprev: np.ndarray, - step_size: float, - momentum_coeff: float, - ) -> tuple[np.ndarray, np.ndarray]: - """ - Updates full parameter array based on a step that is a convex - combination of the gradient and previous momentum - - Args: - params: Current value of the parameters to evaluate the objective function at - gradient: Gradient of objective wrt parameters - mprev: Momentum vector for each parameter - step_size: The scaling of step to take - momentum_coeff: Bias towards previous momentum vector when updating current - momentum/step vector - - Returns: - Tuple of the updated parameter and momentum vectors respectively. - """ - # Momentum update: - # Convex combination of previous momentum and current gradient estimate - mnew = (1 - momentum_coeff) * gradient + momentum_coeff * mprev - params -= step_size * mnew - return params, mnew - - def _converged_objective(self, objval: float, tol: float, window_size: int) -> bool: - """ - Tests convergence based on the change in a moving windowed average of past objective values - - Args: - objval: Current value of the objective function - tol: tolerance below which (average) objective function change must be - window_size: size of averaging window - - Returns: - Bool indicating whether or not the optimization has converged. - """ - # If we haven't reached the required window length, - # append the current value, but we haven't converged - if len(self._prev_loss) < window_size: - self._prev_loss.append(objval) - return False - - # Update last value in list with current value - self._prev_loss.append(objval) - # (length now = n+1) - - # Calculate previous windowed average - # and current windowed average of objective values - prev_avg = np.mean(self._prev_loss[:window_size]) - curr_avg = np.mean(self._prev_loss[1 : window_size + 1]) - self._avg_objval = curr_avg - - # Update window of objective values - # (Remove earliest value) - self._prev_loss.pop(0) - - if np.absolute(prev_avg - curr_avg) < tol: - # converged - logger.info("Previous obj avg: %f\nCurr obj avg: %f", prev_avg, curr_avg) - return True - return False - - def _converged_parameter(self, parameter: np.ndarray, tol: float) -> bool: - """ - Tests convergence based on change in parameter - - Args: - parameter: current parameter values - tol: tolerance for change in norm of parameters - - Returns: - Bool indicating whether or not the optimization has converged - """ - if self._prev_param is None: - self._prev_param = np.copy(parameter) - return False - - order = np.inf - p_change = np.linalg.norm(self._prev_param - parameter, ord=order) - if p_change < tol: - # converged - logger.info("Change in parameters (%f norm): %f", order, p_change) - return True - return False - - def _converged_alt(self, gradient: list[float], tol: float, window_size: int) -> bool: - """ - Tests convergence from norm of windowed average of gradients - - Args: - gradient: current gradient - tol: tolerance for average gradient norm - window_size: size of averaging window - - Returns: - Bool indicating whether or not the optimization has converged - """ - # If we haven't reached the required window length, - # append the current value, but we haven't converged - if len(self._prev_grad) < window_size - 1: - self._prev_grad.append(gradient) - return False - - # Update last value in list with current value - self._prev_grad.append(gradient) - # (length now = n) - - # Calculate previous windowed average - # and current windowed average of objective values - avg_grad = np.mean(self._prev_grad, axis=0) - - # Update window of values - # (Remove earliest value) - self._prev_grad.pop(0) - - if np.linalg.norm(avg_grad, ord=np.inf) < tol: - # converged - logger.info("Avg. grad. norm: %f", np.linalg.norm(avg_grad, ord=np.inf)) - return True - return False - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - params = np.asarray(x0) - momentum = np.zeros(shape=(params.size,)) - # empty out history of previous objectives/gradients/parameters - # (in case this object is re-used) - self._prev_loss = [] - self._prev_grad = [] - self._prev_param = None - self._eval_count = 0 # function evaluations - - iter_count = 0 - logger.info("Initial Params: %s", params) - - epoch = 0 - converged = False - for (eta, mom_coeff) in zip(self._eta, self._momenta_coeff): - logger.info("Epoch: %4d | Stepsize: %6.4f | Momentum: %6.4f", epoch, eta, mom_coeff) - - sum_max_iters = sum(self._maxiter[0 : epoch + 1]) - while iter_count < sum_max_iters: - # update the iteration count - iter_count += 1 - - # Check for parameter convergence before potentially costly function evaluation - converged = self._converged_parameter(params, self._param_tol) - if converged: - break - - # Calculate objective function and estimate of analytical gradient - if jac is None: - objval, gradient = self._compute_objective_fn_and_gradient(params, fun) - else: - objval = fun(params) - gradient = jac(params) - - logger.info( - " Iter: %4d | Obj: %11.6f | Grad Norm: %f", - iter_count, - objval, - np.linalg.norm(gradient, ord=np.inf), - ) - - # Check for objective convergence - converged = self._converged_objective(objval, self._tol, self._averaging) - if converged: - break - - # Update parameters and momentum - params, momentum = self._update(params, gradient, momentum, eta, mom_coeff) - # end inner iteration - # if converged, end iterating over epochs - if converged: - break - epoch += 1 - # end epoch iteration - - result = OptimizerResult() - result.x = params - result.fun = objval - result.nfev = self._eval_count - result.nit = iter_count - - return result diff --git a/qiskit/algorithms/optimizers/bobyqa.py b/qiskit/algorithms/optimizers/bobyqa.py deleted file mode 100644 index 39250aef917a..000000000000 --- a/qiskit/algorithms/optimizers/bobyqa.py +++ /dev/null @@ -1,84 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Bound Optimization BY Quadratic Approximation (BOBYQA) optimizer.""" - -from __future__ import annotations - -from collections.abc import Callable -from typing import Any - -import numpy as np -from qiskit.utils import optionals as _optionals -from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT - - -@_optionals.HAS_SKQUANT.require_in_instance -class BOBYQA(Optimizer): - """Bound Optimization BY Quadratic Approximation algorithm. - - BOBYQA finds local solutions to nonlinear, non-convex minimization problems with optional - bound constraints, without requirement of derivatives of the objective function. - - Uses skquant.opt installed with pip install scikit-quant. - For further detail, please refer to - https://github.com/scikit-quant/scikit-quant and https://qat4chem.lbl.gov/software. - """ - - def __init__( - self, - maxiter: int = 1000, - ) -> None: - """ - Args: - maxiter: Maximum number of function evaluations. - - Raises: - MissingOptionalLibraryError: scikit-quant not installed - """ - super().__init__() - self._maxiter = maxiter - - def get_support_level(self): - """Returns support level dictionary.""" - return { - "gradient": OptimizerSupportLevel.ignored, - "bounds": OptimizerSupportLevel.required, - "initial_point": OptimizerSupportLevel.required, - } - - @property - def settings(self) -> dict[str, Any]: - return {"maxiter": self._maxiter} - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - from skquant import opt as skq - - res, history = skq.minimize( - func=fun, - x0=np.asarray(x0), - bounds=np.array(bounds), - budget=self._maxiter, - method="bobyqa", - ) - - optimizer_result = OptimizerResult() - optimizer_result.x = res.optpar - optimizer_result.fun = res.optval - optimizer_result.nfev = len(history) - return optimizer_result diff --git a/qiskit/algorithms/optimizers/cg.py b/qiskit/algorithms/optimizers/cg.py deleted file mode 100644 index 670b4ac33868..000000000000 --- a/qiskit/algorithms/optimizers/cg.py +++ /dev/null @@ -1,70 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Conjugate Gradient optimizer.""" - -from __future__ import annotations - -from .scipy_optimizer import SciPyOptimizer - - -class CG(SciPyOptimizer): - """Conjugate Gradient optimizer. - - CG is an algorithm for the numerical solution of systems of linear equations whose matrices are - symmetric and positive-definite. It is an *iterative algorithm* in that it uses an initial - guess to generate a sequence of improving approximate solutions for a problem, - in which each approximation is derived from the previous ones. It is often used to solve - unconstrained optimization problems, such as energy minimization. - - Uses scipy.optimize.minimize CG. - For further detail, please refer to - https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html - """ - - _OPTIONS = ["maxiter", "disp", "gtol", "eps"] - - # pylint: disable=unused-argument - def __init__( - self, - maxiter: int = 20, - disp: bool = False, - gtol: float = 1e-5, - tol: float | None = None, - eps: float = 1.4901161193847656e-08, - options: dict | None = None, - max_evals_grouped: int = 1, - **kwargs, - ) -> None: - """ - Args: - maxiter: Maximum number of iterations to perform. - disp: Set to True to print convergence messages. - gtol: Gradient norm must be less than gtol before successful termination. - tol: Tolerance for termination. - eps: If jac is approximated, use this value for the step size. - options: A dictionary of solver options. - max_evals_grouped: Max number of default gradient evaluations performed simultaneously. - kwargs: additional kwargs for scipy.optimize.minimize. - """ - if options is None: - options = {} - for k, v in list(locals().items()): - if k in self._OPTIONS: - options[k] = v - super().__init__( - method="CG", - options=options, - tol=tol, - max_evals_grouped=max_evals_grouped, - **kwargs, - ) diff --git a/qiskit/algorithms/optimizers/cobyla.py b/qiskit/algorithms/optimizers/cobyla.py deleted file mode 100644 index 72a0938379e7..000000000000 --- a/qiskit/algorithms/optimizers/cobyla.py +++ /dev/null @@ -1,59 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Constrained Optimization By Linear Approximation optimizer.""" - -from __future__ import annotations - -from .scipy_optimizer import SciPyOptimizer - - -class COBYLA(SciPyOptimizer): - """ - Constrained Optimization By Linear Approximation optimizer. - - COBYLA is a numerical optimization method for constrained problems - where the derivative of the objective function is not known. - - Uses scipy.optimize.minimize COBYLA. - For further detail, please refer to - https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html - """ - - _OPTIONS = ["maxiter", "disp", "rhobeg"] - - # pylint: disable=unused-argument - def __init__( - self, - maxiter: int = 1000, - disp: bool = False, - rhobeg: float = 1.0, - tol: float | None = None, - options: dict | None = None, - **kwargs, - ) -> None: - """ - Args: - maxiter: Maximum number of function evaluations. - disp: Set to True to print convergence messages. - rhobeg: Reasonable initial changes to the variables. - tol: Final accuracy in the optimization (not precisely guaranteed). - This is a lower bound on the size of the trust region. - options: A dictionary of solver options. - kwargs: additional kwargs for scipy.optimize.minimize. - """ - if options is None: - options = {} - for k, v in list(locals().items()): - if k in self._OPTIONS: - options[k] = v - super().__init__(method="COBYLA", options=options, tol=tol, **kwargs) diff --git a/qiskit/algorithms/optimizers/gradient_descent.py b/qiskit/algorithms/optimizers/gradient_descent.py deleted file mode 100644 index bd39ffa83d85..000000000000 --- a/qiskit/algorithms/optimizers/gradient_descent.py +++ /dev/null @@ -1,401 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""A standard gradient descent optimizer.""" -from __future__ import annotations - -from collections.abc import Generator -from dataclasses import dataclass, field -from typing import Any, Callable, SupportsFloat -import numpy as np -from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT -from .steppable_optimizer import AskData, TellData, OptimizerState, SteppableOptimizer -from .optimizer_utils import LearningRate - -CALLBACK = Callable[[int, np.ndarray, float, SupportsFloat], None] - - -@dataclass -class GradientDescentState(OptimizerState): - """State of :class:`~.GradientDescent`. - - Dataclass with all the information of an optimizer plus the learning_rate and the stepsize. - """ - - stepsize: float | None - """Norm of the gradient on the last step.""" - - learning_rate: LearningRate = field(compare=False) - """Learning rate at the current step of the optimization process. - - It behaves like a generator, (use ``next(learning_rate)`` to get the learning rate for the - next step) but it can also return the current learning rate with ``learning_rate.current``. - - """ - - -class GradientDescent(SteppableOptimizer): - r"""The gradient descent minimization routine. - - For a function :math:`f` and an initial point :math:`\vec\theta_0`, the standard (or "vanilla") - gradient descent method is an iterative scheme to find the minimum :math:`\vec\theta^*` of - :math:`f` by updating the parameters in the direction of the negative gradient of :math:`f` - - .. math:: - - \vec\theta_{n+1} = \vec\theta_{n} - \eta_n \vec\nabla f(\vec\theta_{n}), - - for a small learning rate :math:`\eta_n > 0`. - - You can either provide the analytic gradient :math:`\vec\nabla f` as ``jac`` - in the :meth:`~.minimize` method, or, if you do not provide it, use a finite difference - approximation of the gradient. To adapt the size of the perturbation in the finite difference - gradients, set the ``perturbation`` property in the initializer. - - This optimizer supports a callback function. If provided in the initializer, the optimizer - will call the callback in each iteration with the following information in this order: - current number of function values, current parameters, current function value, norm of current - gradient. - - Examples: - - A minimum example that will use finite difference gradients with a default perturbation - of 0.01 and a default learning rate of 0.01. - - .. code-block:: python - - from qiskit.algorithms.optimizers import GradientDescent - - def f(x): - return (np.linalg.norm(x) - 1) ** 2 - - initial_point = np.array([1, 0.5, -0.2]) - - optimizer = GradientDescent(maxiter=100) - - result = optimizer.minimize(fun=fun, x0=initial_point) - - print(f"Found minimum {result.x} at a value" - "of {result.fun} using {result.nfev} evaluations.") - - An example where the learning rate is an iterator and we supply the analytic gradient. - Note how much faster this convergences (i.e. less ``nfev``) compared to the previous - example. - - .. code-block:: python - - from qiskit.algorithms.optimizers import GradientDescent - - def learning_rate(): - power = 0.6 - constant_coeff = 0.1 - def powerlaw(): - n = 0 - while True: - yield constant_coeff * (n ** power) - n += 1 - - return powerlaw() - - def f(x): - return (np.linalg.norm(x) - 1) ** 2 - - def grad_f(x): - return 2 * (np.linalg.norm(x) - 1) * x / np.linalg.norm(x) - - initial_point = np.array([1, 0.5, -0.2]) - - optimizer = GradientDescent(maxiter=100, learning_rate=learning_rate) - result = optimizer.minimize(fun=fun, jac=grad_f, x0=initial_point) - - print(f"Found minimum {result.x} at a value" - "of {result.fun} using {result.nfev} evaluations.") - - - An other example where the evaluation of the function has a chance of failing. The user, with - specific knowledge about his function can catch this errors and handle them before passing the - result to the optimizer. - - .. code-block:: python - - import random - import numpy as np - from qiskit.algorithms.optimizers import GradientDescent - - def objective(x): - if random.choice([True, False]): - return None - else: - return (np.linalg.norm(x) - 1) ** 2 - - def grad(x): - if random.choice([True, False]): - return None - else: - return 2 * (np.linalg.norm(x) - 1) * x / np.linalg.norm(x) - - - initial_point = np.random.normal(0, 1, size=(100,)) - - optimizer = GradientDescent(maxiter=20) - optimizer.start(x0=initial_point, fun=objective, jac=grad) - - while optimizer.continue_condition(): - ask_data = optimizer.ask() - evaluated_gradient = None - - while evaluated_gradient is None: - evaluated_gradient = grad(ask_data.x_center) - optimizer.state.njev += 1 - - optmizer.state.nit += 1 - - tell_data = TellData(eval_jac=evaluated_gradient) - optimizer.tell(ask_data=ask_data, tell_data=tell_data) - - result = optimizer.create_result() - - Users that aren't dealing with complicated functions and who are more familiar with step by step - optimization algorithms can use the :meth:`~.step` method which wraps the :meth:`~.ask` - and :meth:`~.tell` methods. In the same spirit the method :meth:`~.minimize` will optimize the - function and return the result. - - To see other libraries that use this interface one can visit: - https://optuna.readthedocs.io/en/stable/tutorial/20_recipes/009_ask_and_tell.html - - """ - - def __init__( - self, - maxiter: int = 100, - learning_rate: float - | list[float] - | np.ndarray - | Callable[[], Generator[float, None, None]] = 0.01, - tol: float = 1e-7, - callback: CALLBACK | None = None, - perturbation: float | None = None, - ) -> None: - """ - Args: - maxiter: The maximum number of iterations. - learning_rate: A constant, list, array or factory of generators yielding learning rates - for the parameter updates. See the docstring for an example. - tol: If the norm of the parameter update is smaller than this threshold, the - optimizer has converged. - perturbation: If no gradient is passed to :meth:`~.minimize` the gradient is - approximated with a forward finite difference scheme with ``perturbation`` - perturbation in both directions (defaults to 1e-2 if required). - Ignored when we have an explicit function for the gradient. - Raises: - ValueError: If ``learning_rate`` is an array and its length is less than ``maxiter``. - """ - super().__init__(maxiter=maxiter) - self.callback = callback - self._state: GradientDescentState | None = None - self._perturbation = perturbation - self._tol = tol - # if learning rate is an array, check it is sufficiently long. - if isinstance(learning_rate, (list, np.ndarray)): - if len(learning_rate) < maxiter: - raise ValueError( - f"Length of learning_rate ({len(learning_rate)}) " - f"is smaller than maxiter ({maxiter})." - ) - self.learning_rate = learning_rate - - @property - def state(self) -> GradientDescentState: - """Return the current state of the optimizer.""" - return self._state - - @state.setter - def state(self, state: GradientDescentState) -> None: - """Set the current state of the optimizer.""" - self._state = state - - @property - def tol(self) -> float: - """Returns the tolerance of the optimizer. - - Any step with smaller stepsize than this value will stop the optimization.""" - return self._tol - - @tol.setter - def tol(self, tol: float) -> None: - """Set the tolerance.""" - self._tol = tol - - @property - def perturbation(self) -> float | None: - """Returns the perturbation. - - This is the perturbation used in the finite difference gradient approximation. - """ - return self._perturbation - - @perturbation.setter - def perturbation(self, perturbation: float | None) -> None: - """Set the perturbation.""" - self._perturbation = perturbation - - def _callback_wrapper(self) -> None: - """ - Wraps the callback function to accommodate GradientDescent. - - Will call :attr:`~.callback` and pass the following arguments: - current number of function values, current parameters, current function value, - norm of current gradient. - """ - if self.callback is not None: - self.callback( - self.state.nfev, - self.state.x, - self.state.fun(self.state.x), - self.state.stepsize, - ) - - @property - def settings(self) -> dict[str, Any]: - # if learning rate or perturbation are custom iterators expand them - if callable(self.learning_rate): - iterator = self.learning_rate() - learning_rate: float | np.ndarray = np.array( - [next(iterator) for _ in range(self.maxiter)] - ) - else: - learning_rate = self.learning_rate - - return { - "maxiter": self.maxiter, - "tol": self.tol, - "learning_rate": learning_rate, - "perturbation": self.perturbation, - "callback": self.callback, - } - - def ask(self) -> AskData: - """Returns an object with the data needed to evaluate the gradient. - - If this object contains a gradient function the gradient can be evaluated directly. Otherwise - approximate it with a finite difference scheme. - """ - return AskData( - x_jac=self.state.x, - ) - - def tell(self, ask_data: AskData, tell_data: TellData) -> None: - """ - Updates :attr:`.~GradientDescentState.x` by an amount proportional to the learning - rate and value of the gradient at that point. - - Args: - ask_data: The data used to evaluate the function. - tell_data: The data from the function evaluation. - - Raises: - ValueError: If the gradient passed doesn't have the right dimension. - """ - if np.shape(self.state.x) != np.shape(tell_data.eval_jac): - raise ValueError("The gradient does not have the correct dimension") - self.state.x = self.state.x - next(self.state.learning_rate) * tell_data.eval_jac - self.state.stepsize = np.linalg.norm(tell_data.eval_jac) - self.state.nit += 1 - - def evaluate(self, ask_data: AskData) -> TellData: - """Evaluates the gradient. - - It does so either by evaluating an analytic gradient or by approximating it with a - finite difference scheme. It will either add ``1`` to the number of gradient evaluations or add - ``N+1`` to the number of function evaluations (Where N is the dimension of the gradient). - - Args: - ask_data: It contains the point where the gradient is to be evaluated and the gradient - function or, in its absence, the objective function to perform a finite difference - approximation. - - Returns: - The data containing the gradient evaluation. - """ - if self.state.jac is None: - eps = 0.01 if (self.perturbation is None) else self.perturbation - grad = Optimizer.gradient_num_diff( - x_center=ask_data.x_jac, - f=self.state.fun, - epsilon=eps, - max_evals_grouped=self._max_evals_grouped, - ) - self.state.nfev += 1 + len(ask_data.x_jac) - else: - grad = self.state.jac(ask_data.x_jac) - self.state.njev += 1 - - return TellData(eval_jac=grad) - - def create_result(self) -> OptimizerResult: - """Creates a result of the optimization process. - - This result contains the best point, the best function value, the number of function/gradient - evaluations and the number of iterations. - - Returns: - The result of the optimization process. - """ - result = OptimizerResult() - result.x = self.state.x - result.fun = self.state.fun(self.state.x) - result.nfev = self.state.nfev - result.njev = self.state.njev - result.nit = self.state.nit - return result - - def start( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> None: - - self.state = GradientDescentState( - fun=fun, - jac=jac, - x=np.asarray(x0), - nit=0, - nfev=0, - njev=0, - learning_rate=LearningRate(learning_rate=self.learning_rate), - stepsize=None, - ) - - def continue_condition(self) -> bool: - """ - Condition that indicates the optimization process should come to an end. - - When the stepsize is smaller than the tolerance, the optimization process is considered - finished. - - Returns: - ``True`` if the optimization process should continue, ``False`` otherwise. - """ - if self.state.stepsize is None: - return True - else: - return (self.state.stepsize > self.tol) and super().continue_condition() - - def get_support_level(self): - """Get the support level dictionary.""" - return { - "gradient": OptimizerSupportLevel.supported, - "bounds": OptimizerSupportLevel.ignored, - "initial_point": OptimizerSupportLevel.required, - } diff --git a/qiskit/algorithms/optimizers/gsls.py b/qiskit/algorithms/optimizers/gsls.py deleted file mode 100644 index 6ae423fb52c8..000000000000 --- a/qiskit/algorithms/optimizers/gsls.py +++ /dev/null @@ -1,378 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Line search with Gaussian-smoothed samples on a sphere.""" - -from __future__ import annotations - -import warnings - -from collections.abc import Callable -from typing import Any, SupportsFloat -import numpy as np - -from qiskit.utils import algorithm_globals -from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT - - -class GSLS(Optimizer): - """Gaussian-smoothed Line Search. - - An implementation of the line search algorithm described in - https://arxiv.org/pdf/1905.01332.pdf, using gradient approximation - based on Gaussian-smoothed samples on a sphere. - - .. note:: - - This component has some function that is normally random. If you want to reproduce behavior - then you should set the random number generator seed in the algorithm_globals - (``qiskit.utils.algorithm_globals.random_seed = seed``). - """ - - _OPTIONS = [ - "maxiter", - "max_eval", - "disp", - "sampling_radius", - "sample_size_factor", - "initial_step_size", - "min_step_size", - "step_size_multiplier", - "armijo_parameter", - "min_gradient_norm", - "max_failed_rejection_sampling", - ] - - # pylint: disable=unused-argument - def __init__( - self, - maxiter: int = 10000, - max_eval: int = 10000, - disp: bool = False, - sampling_radius: float = 1.0e-6, - sample_size_factor: int = 1, - initial_step_size: float = 1.0e-2, - min_step_size: float = 1.0e-10, - step_size_multiplier: float = 0.4, - armijo_parameter: float = 1.0e-1, - min_gradient_norm: float = 1e-8, - max_failed_rejection_sampling: int = 50, - ) -> None: - """ - Args: - maxiter: Maximum number of iterations. - max_eval: Maximum number of evaluations. - disp: Set to True to display convergence messages. - sampling_radius: Sampling radius to determine gradient estimate. - sample_size_factor: The size of the sample set at each iteration is this number - multiplied by the dimension of the problem, rounded to the nearest integer. - initial_step_size: Initial step size for the descent algorithm. - min_step_size: Minimum step size for the descent algorithm. - step_size_multiplier: Step size reduction after unsuccessful steps, in the - interval (0, 1). - armijo_parameter: Armijo parameter for sufficient decrease criterion, in the - interval (0, 1). - min_gradient_norm: If the gradient norm is below this threshold, the algorithm stops. - max_failed_rejection_sampling: Maximum number of attempts to sample points within - bounds. - """ - super().__init__() - for k, v in list(locals().items()): - if k in self._OPTIONS: - self._options[k] = v - - def get_support_level(self) -> dict[str, int]: - """Return support level dictionary. - - Returns: - A dictionary containing the support levels for different options. - """ - return { - "gradient": OptimizerSupportLevel.ignored, - "bounds": OptimizerSupportLevel.supported, - "initial_point": OptimizerSupportLevel.required, - } - - @property - def settings(self) -> dict[str, Any]: - return {key: self._options.get(key, None) for key in self._OPTIONS} - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - if not isinstance(x0, np.ndarray): - x0 = np.asarray(x0) - - if bounds is None: - var_lb = np.array([-np.inf] * x0.size) - var_ub = np.array([np.inf] * x0.size) - else: - var_lb = np.array([l for (l, _) in bounds]) - var_ub = np.array([u for (_, u) in bounds]) - - x, fun, nfev, _ = self.ls_optimize(x0.size, fun, x0, var_lb, var_ub) - - result = OptimizerResult() - result.x = x - result.fun = fun - result.nfev = nfev - - return result - - def ls_optimize( - self, - n: int, - obj_fun: Callable[[POINT], float], - initial_point: np.ndarray, - var_lb: np.ndarray, - var_ub: np.ndarray, - ) -> tuple[np.ndarray, float, int, float]: - """Run the line search optimization. - - Args: - n: Dimension of the problem. - obj_fun: Objective function. - initial_point: Initial point. - var_lb: Vector of lower bounds on the decision variables. Vector elements can be -np.inf - if the corresponding variable is unbounded from below. - var_ub: Vector of upper bounds on the decision variables. Vector elements can be np.inf - if the corresponding variable is unbounded from below. - - Returns: - Final iterate as a vector, corresponding objective function value, - number of evaluations, and norm of the gradient estimate. - - Raises: - ValueError: If the number of dimensions mismatches the size of the initial point or - the length of the lower or upper bound. - """ - if len(initial_point) != n: - raise ValueError("Size of the initial point mismatches the number of dimensions.") - if len(var_lb) != n: - raise ValueError("Length of the lower bound mismatches the number of dimensions.") - if len(var_ub) != n: - raise ValueError("Length of the upper bound mismatches the number of dimensions.") - - # Initialize counters and data - iter_count = 0 - n_evals = 0 - prev_iter_successful = True - prev_directions, prev_sample_set_x, prev_sample_set_y = None, None, None - consecutive_fail_iter = 0 - alpha = self._options["initial_step_size"] - grad_norm: SupportsFloat = np.inf - sample_set_size = int(round(self._options["sample_size_factor"] * n)) - - # Initial point - x = initial_point - x_value = obj_fun(x) - n_evals += 1 - while iter_count < self._options["maxiter"] and n_evals < self._options["max_eval"]: - - # Determine set of sample points - directions, sample_set_x = self.sample_set(n, x, var_lb, var_ub, sample_set_size) - - if n_evals + len(sample_set_x) + 1 >= self._options["max_eval"]: - # The evaluation budget is too small to allow for - # another full iteration; we therefore exit now - break - - sample_set_y = np.array([obj_fun(point) for point in sample_set_x]) - n_evals += len(sample_set_x) - - # Expand sample set if we could not improve - if not prev_iter_successful: - directions = np.vstack((prev_directions, directions)) - sample_set_x = np.vstack((prev_sample_set_x, sample_set_x)) - sample_set_y = np.hstack((prev_sample_set_y, sample_set_y)) - - # Find gradient approximation and candidate point - grad = self.gradient_approximation( - n, x, x_value, directions, sample_set_x, sample_set_y - ) - grad_norm = np.linalg.norm(grad) - new_x = np.clip(x - alpha * grad, var_lb, var_ub) - new_x_value = obj_fun(new_x) - n_evals += 1 - - # Print information - if self._options["disp"]: - print(f"Iter {iter_count:d}") - print(f"Point {x} obj {x_value}") - print(f"Gradient {grad}") - print(f"Grad norm {grad_norm} new_x_value {new_x_value} step_size {alpha}") - print(f"Direction {directions}") - - # Test Armijo condition for sufficient decrease - if new_x_value <= x_value - self._options["armijo_parameter"] * alpha * grad_norm: - # Accept point - x, x_value = new_x, new_x_value - alpha /= 2 * self._options["step_size_multiplier"] - prev_iter_successful = True - consecutive_fail_iter = 0 - - # Reset sample set - prev_directions = None - prev_sample_set_x = None - prev_sample_set_y = None - else: - # Do not accept point - alpha *= self._options["step_size_multiplier"] - prev_iter_successful = False - consecutive_fail_iter += 1 - - # Store sample set to enlarge it - prev_directions = directions - prev_sample_set_x, prev_sample_set_y = sample_set_x, sample_set_y - - iter_count += 1 - - # Check termination criterion - if ( - grad_norm <= self._options["min_gradient_norm"] - or alpha <= self._options["min_step_size"] - ): - break - - return x, x_value, n_evals, grad_norm - - def sample_points( - self, n: int, x: np.ndarray, num_points: int - ) -> tuple[np.ndarray, np.ndarray]: - """Sample ``num_points`` points around ``x`` on the ``n``-sphere of specified radius. - - The radius of the sphere is ``self._options['sampling_radius']``. - - Args: - n: Dimension of the problem. - x: Point around which the sample set is constructed. - num_points: Number of points in the sample set. - - Returns: - A tuple containing the sampling points and the directions. - """ - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - normal_samples = algorithm_globals.random.normal(size=(num_points, n)) - row_norms = np.linalg.norm(normal_samples, axis=1, keepdims=True) - directions = normal_samples / row_norms - points = x + self._options["sampling_radius"] * directions - - return points, directions - - def sample_set( - self, n: int, x: np.ndarray, var_lb: np.ndarray, var_ub: np.ndarray, num_points: int - ) -> tuple[np.ndarray, np.ndarray]: - """Construct sample set of given size. - - Args: - n: Dimension of the problem. - x: Point around which the sample set is constructed. - var_lb: Vector of lower bounds on the decision variables. Vector elements can be -np.inf - if the corresponding variable is unbounded from below. - var_ub: Vector of lower bounds on the decision variables. Vector elements can be np.inf - if the corresponding variable is unbounded from above. - num_points: Number of points in the sample set. - - Returns: - Matrices of (unit-norm) sample directions and sample points, one per row. - Both matrices are 2D arrays of floats. - - Raises: - RuntimeError: If not enough samples could be generated within the bounds. - """ - # Generate points uniformly on the sphere - points, directions = self.sample_points(n, x, num_points) - - # Check bounds - if (points >= var_lb).all() and (points <= var_ub).all(): - # If all points are within bounds, return them - return directions, (x + self._options["sampling_radius"] * directions) - else: - # Otherwise we perform rejection sampling until we have - # enough points that satisfy the bounds - indices = np.where((points >= var_lb).all(axis=1) & (points <= var_ub).all(axis=1))[0] - accepted = directions[indices] - num_trials = 0 - - while ( - len(accepted) < num_points - and num_trials < self._options["max_failed_rejection_sampling"] - ): - # Generate points uniformly on the sphere - points, directions = self.sample_points(n, x, num_points) - indices = np.where((points >= var_lb).all(axis=1) & (points <= var_ub).all(axis=1))[ - 0 - ] - accepted = np.vstack((accepted, directions[indices])) - num_trials += 1 - - # When we are at a corner point, the expected fraction of acceptable points may be - # exponential small in the dimension of the problem. Thus, if we keep failing and - # do not have enough points by now, we switch to a different method that guarantees - # finding enough points, but they may not be uniformly distributed. - if len(accepted) < num_points: - points, directions = self.sample_points(n, x, num_points) - to_be_flipped = (points < var_lb) | (points > var_ub) - directions *= np.where(to_be_flipped, -1, 1) - points = x + self._options["sampling_radius"] * directions - indices = np.where((points >= var_lb).all(axis=1) & (points <= var_ub).all(axis=1))[ - 0 - ] - accepted = np.vstack((accepted, directions[indices])) - - # If we still do not have enough sampling points, we have failed. - if len(accepted) < num_points: - raise RuntimeError( - "Could not generate enough samples within bounds; try smaller radius." - ) - - return ( - accepted[:num_points], - x + self._options["sampling_radius"] * accepted[:num_points], - ) - - def gradient_approximation( - self, - n: int, - x: np.ndarray, - x_value: float, - directions: np.ndarray, - sample_set_x: np.ndarray, - sample_set_y: np.ndarray, - ) -> np.ndarray: - """Construct gradient approximation from given sample. - - Args: - n: Dimension of the problem. - x: Point around which the sample set was constructed. - x_value: Objective function value at x. - directions: Directions of the sample points wrt the central point x, as a 2D array. - sample_set_x: x-coordinates of the sample set, one point per row, as a 2D array. - sample_set_y: Objective function values of the points in sample_set_x, as a 1D array. - - Returns: - Gradient approximation at x, as a 1D array. - """ - ffd = sample_set_y - x_value - gradient = ( - float(n) - / len(sample_set_y) - * np.sum( - ffd.reshape(len(sample_set_y), 1) / self._options["sampling_radius"] * directions, 0 - ) - ) - return gradient diff --git a/qiskit/algorithms/optimizers/imfil.py b/qiskit/algorithms/optimizers/imfil.py deleted file mode 100644 index 2fca4da2c139..000000000000 --- a/qiskit/algorithms/optimizers/imfil.py +++ /dev/null @@ -1,86 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""IMplicit FILtering (IMFIL) optimizer.""" -from __future__ import annotations - -from collections.abc import Callable -from typing import Any - -from qiskit.utils import optionals as _optionals -from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT - - -@_optionals.HAS_SKQUANT.require_in_instance -class IMFIL(Optimizer): - """IMplicit FILtering algorithm. - - Implicit filtering is a way to solve bound-constrained optimization problems for - which derivatives are not available. In comparison to methods that use interpolation to - reconstruct the function and its higher derivatives, implicit filtering builds upon - coordinate search followed by interpolation to get an approximate gradient. - - Uses skquant.opt installed with pip install scikit-quant. - For further detail, please refer to - https://github.com/scikit-quant/scikit-quant and https://qat4chem.lbl.gov/software. - """ - - def __init__( - self, - maxiter: int = 1000, - ) -> None: - """ - Args: - maxiter: Maximum number of function evaluations. - - Raises: - MissingOptionalLibraryError: scikit-quant not installed - """ - super().__init__() - self._maxiter = maxiter - - def get_support_level(self): - """Returns support level dictionary.""" - return { - "gradient": OptimizerSupportLevel.ignored, - "bounds": OptimizerSupportLevel.required, - "initial_point": OptimizerSupportLevel.required, - } - - @property - def settings(self) -> dict[str, Any]: - return { - "maxiter": self._maxiter, - } - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - from skquant import opt as skq - - res, history = skq.minimize( - func=fun, - x0=x0, - bounds=bounds, - budget=self._maxiter, - method="imfil", - ) - - optimizer_result = OptimizerResult() - optimizer_result.x = res.optpar - optimizer_result.fun = res.optval - optimizer_result.nfev = len(history) - return optimizer_result diff --git a/qiskit/algorithms/optimizers/l_bfgs_b.py b/qiskit/algorithms/optimizers/l_bfgs_b.py deleted file mode 100644 index 3c2d7a619ef3..000000000000 --- a/qiskit/algorithms/optimizers/l_bfgs_b.py +++ /dev/null @@ -1,88 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Limited-memory BFGS Bound optimizer.""" - -from __future__ import annotations -from typing import SupportsFloat - -import numpy as np - -from .scipy_optimizer import SciPyOptimizer - - -class L_BFGS_B(SciPyOptimizer): # pylint: disable=invalid-name - """ - Limited-memory BFGS Bound optimizer. - - The target goal of Limited-memory Broyden-Fletcher-Goldfarb-Shanno Bound (L-BFGS-B) - is to minimize the value of a differentiable scalar function :math:`f`. - This optimizer is a quasi-Newton method, meaning that, in contrast to Newtons's method, - it does not require :math:`f`'s Hessian (the matrix of :math:`f`'s second derivatives) - when attempting to compute :math:`f`'s minimum value. - - Like BFGS, L-BFGS is an iterative method for solving unconstrained, non-linear optimization - problems, but approximates BFGS using a limited amount of computer memory. - L-BFGS starts with an initial estimate of the optimal value, and proceeds iteratively - to refine that estimate with a sequence of better estimates. - - The derivatives of :math:`f` are used to identify the direction of steepest descent, - and also to form an estimate of the Hessian matrix (second derivative) of :math:`f`. - L-BFGS-B extends L-BFGS to handle simple, per-variable bound constraints. - - Uses ``scipy.optimize.fmin_l_bfgs_b``. - For further detail, please refer to - https://docs.scipy.org/doc/scipy/reference/optimize.minimize-lbfgsb.html - """ - - _OPTIONS = ["maxfun", "maxiter", "ftol", "iprint", "eps"] - - # pylint: disable=unused-argument - def __init__( - self, - maxfun: int = 15000, - maxiter: int = 15000, - ftol: SupportsFloat = 10 * np.finfo(float).eps, - iprint: int = -1, - eps: float = 1e-08, - options: dict | None = None, - max_evals_grouped: int = 1, - **kwargs, - ): - r""" - Args: - maxfun: Maximum number of function evaluations. - maxiter: Maximum number of iterations. - ftol: The iteration stops when - :math:`(f^k - f^{k+1}) / \max\{|f^k|, |f^{k+1}|,1\} \leq \text{ftol}`. - iprint: Controls the frequency of output. ``iprint < 0`` means no output; - ``iprint = 0`` print only one line at the last iteration; ``0 < iprint < 99`` - print also :math:`f` and :math:`|\text{proj} g|` every iprint iterations; - ``iprint = 99`` print details of every iteration except n-vectors; ``iprint = 100`` - print also the changes of active set and final :math:`x`; ``iprint > 100`` print - details of every iteration including :math:`x` and :math:`g`. - eps: If jac is approximated, use this value for the step size. - options: A dictionary of solver options. - max_evals_grouped: Max number of default gradient evaluations performed simultaneously. - kwargs: additional kwargs for ``scipy.optimize.minimize``. - """ - if options is None: - options = {} - for k, v in list(locals().items()): - if k in self._OPTIONS: - options[k] = v - super().__init__( - method="L-BFGS-B", - options=options, - max_evals_grouped=max_evals_grouped, - **kwargs, - ) diff --git a/qiskit/algorithms/optimizers/nelder_mead.py b/qiskit/algorithms/optimizers/nelder_mead.py deleted file mode 100644 index ff9d8708763f..000000000000 --- a/qiskit/algorithms/optimizers/nelder_mead.py +++ /dev/null @@ -1,73 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Nelder-Mead optimizer.""" -from __future__ import annotations - - -from .scipy_optimizer import SciPyOptimizer - - -class NELDER_MEAD(SciPyOptimizer): # pylint: disable=invalid-name - """ - Nelder-Mead optimizer. - - The Nelder-Mead algorithm performs unconstrained optimization; it ignores bounds - or constraints. It is used to find the minimum or maximum of an objective function - in a multidimensional space. It is based on the Simplex algorithm. Nelder-Mead - is robust in many applications, especially when the first and second derivatives of the - objective function are not known. - - However, if the numerical computation of the derivatives can be trusted to be accurate, - other algorithms using the first and/or second derivatives information might be preferred to - Nelder-Mead for their better performance in the general case, especially in consideration of - the fact that the Nelder–Mead technique is a heuristic search method that can converge to - non-stationary points. - - Uses scipy.optimize.minimize Nelder-Mead. - For further detail, please refer to - See https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html - """ - - _OPTIONS = ["maxiter", "maxfev", "disp", "xatol", "adaptive"] - - # pylint: disable=unused-argument - def __init__( - self, - maxiter: int | None = None, - maxfev: int = 1000, - disp: bool = False, - xatol: float = 0.0001, - tol: float | None = None, - adaptive: bool = False, - options: dict | None = None, - **kwargs, - ) -> None: - """ - Args: - maxiter: Maximum allowed number of iterations. If both maxiter and maxfev are set, - minimization will stop at the first reached. - maxfev: Maximum allowed number of function evaluations. If both maxiter and - maxfev are set, minimization will stop at the first reached. - disp: Set to True to print convergence messages. - xatol: Absolute error in xopt between iterations that is acceptable for convergence. - tol: Tolerance for termination. - adaptive: Adapt algorithm parameters to dimensionality of problem. - options: A dictionary of solver options. - kwargs: additional kwargs for scipy.optimize.minimize. - """ - if options is None: - options = {} - for k, v in list(locals().items()): - if k in self._OPTIONS: - options[k] = v - super().__init__(method="Nelder-Mead", options=options, tol=tol, **kwargs) diff --git a/qiskit/algorithms/optimizers/nft.py b/qiskit/algorithms/optimizers/nft.py deleted file mode 100644 index 2a7503137daf..000000000000 --- a/qiskit/algorithms/optimizers/nft.py +++ /dev/null @@ -1,170 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Nakanishi-Fujii-Todo algorithm.""" -from __future__ import annotations - - -import numpy as np -from scipy.optimize import OptimizeResult - -from .scipy_optimizer import SciPyOptimizer - - -class NFT(SciPyOptimizer): - """ - Nakanishi-Fujii-Todo algorithm. - - See https://arxiv.org/abs/1903.12166 - """ - - _OPTIONS = ["maxiter", "maxfev", "disp", "reset_interval"] - - # pylint: disable=unused-argument - def __init__( - self, - maxiter: int | None = None, - maxfev: int = 1024, - disp: bool = False, - reset_interval: int = 32, - options: dict | None = None, - **kwargs, - ) -> None: - """ - Built out using scipy framework, for details, please refer to - https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html. - - Args: - maxiter: Maximum number of iterations to perform. - maxfev: Maximum number of function evaluations to perform. - disp: disp - reset_interval: The minimum estimates directly once - in ``reset_interval`` times. - options: A dictionary of solver options. - kwargs: additional kwargs for scipy.optimize.minimize. - - Notes: - In this optimization method, the optimization function have to satisfy - three conditions written in [1]_. - - References: - .. [1] K. M. Nakanishi, K. Fujii, and S. Todo. 2019. - Sequential minimal optimization for quantum-classical hybrid algorithms. - arXiv preprint arXiv:1903.12166. - """ - if options is None: - options = {} - for k, v in list(locals().items()): - if k in self._OPTIONS: - options[k] = v - super().__init__(method=nakanishi_fujii_todo, options=options, **kwargs) - - -# pylint: disable=invalid-name -def nakanishi_fujii_todo( - fun, x0, args=(), maxiter=None, maxfev=1024, reset_interval=32, eps=1e-32, callback=None, **_ -): - """ - Find the global minimum of a function using the nakanishi_fujii_todo - algorithm [1]. - Args: - fun (callable): ``f(x, *args)`` - Function to be optimized. ``args`` can be passed as an optional item - in the dict ``minimizer_kwargs``. - This function must satisfy the three condition written in Ref. [1]. - x0 (ndarray): shape (n,) - Initial guess. Array of real elements of size (n,), - where 'n' is the number of independent variables. - args (tuple, optional): - Extra arguments passed to the objective function. - maxiter (int): - Maximum number of iterations to perform. - Default: None. - maxfev (int): - Maximum number of function evaluations to perform. - Default: 1024. - reset_interval (int): - The minimum estimates directly once in ``reset_interval`` times. - Default: 32. - eps (float): eps - **_ : additional options - callback (callable, optional): - Called after each iteration. - Returns: - OptimizeResult: - The optimization result represented as a ``OptimizeResult`` object. - Important attributes are: ``x`` the solution array. See - `OptimizeResult` for a description of other attributes. - Notes: - In this optimization method, the optimization function have to satisfy - three conditions written in [2]_. - - References: - .. [2] K. M. Nakanishi, K. Fujii, and S. Todo. 2019. - Sequential minimal optimization for quantum-classical hybrid algorithms. - arXiv preprint arXiv:1903.12166. - """ - - x0 = np.asarray(x0) - recycle_z0 = None - niter = 0 - funcalls = 0 - - while True: - - idx = niter % x0.size - - if reset_interval > 0: - if niter % reset_interval == 0: - recycle_z0 = None - - if recycle_z0 is None: - z0 = fun(np.copy(x0), *args) - funcalls += 1 - else: - z0 = recycle_z0 - - p = np.copy(x0) - p[idx] = x0[idx] + np.pi / 2 - z1 = fun(p, *args) - funcalls += 1 - - p = np.copy(x0) - p[idx] = x0[idx] - np.pi / 2 - z3 = fun(p, *args) - funcalls += 1 - - z2 = z1 + z3 - z0 - c = (z1 + z3) / 2 - a = np.sqrt((z0 - z2) ** 2 + (z1 - z3) ** 2) / 2 - b = np.arctan((z1 - z3) / ((z0 - z2) + eps * (z0 == z2))) + x0[idx] - b += 0.5 * np.pi + 0.5 * np.pi * np.sign((z0 - z2) + eps * (z0 == z2)) - - x0[idx] = b - recycle_z0 = c - a - - niter += 1 - - if callback is not None: - callback(np.copy(x0)) - - if maxfev is not None: - if funcalls >= maxfev: - break - - if maxiter is not None: - if niter >= maxiter: - break - - return OptimizeResult( - fun=fun(np.copy(x0), *args), x=x0, nit=niter, nfev=funcalls, success=(niter > 1) - ) diff --git a/qiskit/algorithms/optimizers/nlopts/__init__.py b/qiskit/algorithms/optimizers/nlopts/__init__.py deleted file mode 100644 index e3165bc0b482..000000000000 --- a/qiskit/algorithms/optimizers/nlopts/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""NLopt based global optimizers""" diff --git a/qiskit/algorithms/optimizers/nlopts/crs.py b/qiskit/algorithms/optimizers/nlopts/crs.py deleted file mode 100644 index 77eb67b298b6..000000000000 --- a/qiskit/algorithms/optimizers/nlopts/crs.py +++ /dev/null @@ -1,35 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Controlled Random Search (CRS) with local mutation optimizer.""" - -from .nloptimizer import NLoptOptimizer, NLoptOptimizerType - - -class CRS(NLoptOptimizer): - """ - Controlled Random Search (CRS) with local mutation optimizer. - - Controlled Random Search (CRS) with local mutation is part of the family of the CRS optimizers. - The CRS optimizers start with a random population of points, and randomly evolve these points - by heuristic rules. In the case of CRS with local mutation, the evolution is a randomized - version of the :class:`NELDER_MEAD` local optimizer. - - - NLopt global optimizer, derivative-free. - For further detail, please refer to - https://nlopt.readthedocs.io/en/latest/NLopt_Algorithms/#controlled-random-search-crs-with-local-mutation - """ - - def get_nlopt_optimizer(self) -> NLoptOptimizerType: - """Return NLopt optimizer type""" - return NLoptOptimizerType.GN_CRS2_LM diff --git a/qiskit/algorithms/optimizers/nlopts/direct_l.py b/qiskit/algorithms/optimizers/nlopts/direct_l.py deleted file mode 100644 index e7ed9be3e25f..000000000000 --- a/qiskit/algorithms/optimizers/nlopts/direct_l.py +++ /dev/null @@ -1,34 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""DIviding RECTangles Locally-biased optimizer.""" - -from .nloptimizer import NLoptOptimizer, NLoptOptimizerType - - -class DIRECT_L(NLoptOptimizer): # pylint: disable=invalid-name - """ - DIviding RECTangles Locally-biased optimizer. - - DIviding RECTangles (DIRECT) is a deterministic-search algorithms based on systematic division - of the search domain into increasingly smaller hyper-rectangles. - The DIRECT-L version is a "locally biased" variant of DIRECT that makes the algorithm more - biased towards local search, so that it is more efficient for functions with few local minima. - - NLopt global optimizer, derivative-free. - For further detail, please refer to - http://nlopt.readthedocs.io/en/latest/NLopt_Algorithms/#direct-and-direct-l - """ - - def get_nlopt_optimizer(self) -> NLoptOptimizerType: - """Return NLopt optimizer type""" - return NLoptOptimizerType.GN_DIRECT_L diff --git a/qiskit/algorithms/optimizers/nlopts/direct_l_rand.py b/qiskit/algorithms/optimizers/nlopts/direct_l_rand.py deleted file mode 100644 index 15172ef00880..000000000000 --- a/qiskit/algorithms/optimizers/nlopts/direct_l_rand.py +++ /dev/null @@ -1,32 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""DIviding RECTangles Locally-biased Randomized optimizer.""" - -from .nloptimizer import NLoptOptimizer, NLoptOptimizerType - - -class DIRECT_L_RAND(NLoptOptimizer): # pylint: disable=invalid-name - """ - DIviding RECTangles Locally-biased Randomized optimizer. - - DIRECT-L RAND is the "locally biased" variant with some randomization in near-tie decisions. - See also :class:`DIRECT_L` - - NLopt global optimizer, derivative-free. - For further detail, please refer to - http://nlopt.readthedocs.io/en/latest/NLopt_Algorithms/#direct-and-direct-l - """ - - def get_nlopt_optimizer(self) -> NLoptOptimizerType: - """Return NLopt optimizer type""" - return NLoptOptimizerType.GN_DIRECT_L_RAND diff --git a/qiskit/algorithms/optimizers/nlopts/esch.py b/qiskit/algorithms/optimizers/nlopts/esch.py deleted file mode 100644 index 7e754f9447fe..000000000000 --- a/qiskit/algorithms/optimizers/nlopts/esch.py +++ /dev/null @@ -1,33 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""ESCH evolutionary optimizer.""" - -from .nloptimizer import NLoptOptimizer, NLoptOptimizerType - - -class ESCH(NLoptOptimizer): - """ - ESCH evolutionary optimizer. - - ESCH is an evolutionary algorithm for global optimization that supports bound constraints only. - Specifically, it does not support nonlinear constraints. - - NLopt global optimizer, derivative-free. - For further detail, please refer to - - http://nlopt.readthedocs.io/en/latest/NLopt_Algorithms/#esch-evolutionary-algorithm - """ - - def get_nlopt_optimizer(self) -> NLoptOptimizerType: - """Return NLopt optimizer type""" - return NLoptOptimizerType.GN_ESCH diff --git a/qiskit/algorithms/optimizers/nlopts/isres.py b/qiskit/algorithms/optimizers/nlopts/isres.py deleted file mode 100644 index 1c37a9401e3a..000000000000 --- a/qiskit/algorithms/optimizers/nlopts/isres.py +++ /dev/null @@ -1,39 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Improved Stochastic Ranking Evolution Strategy optimizer.""" - -from .nloptimizer import NLoptOptimizer, NLoptOptimizerType - - -class ISRES(NLoptOptimizer): - """ - Improved Stochastic Ranking Evolution Strategy optimizer. - - Improved Stochastic Ranking Evolution Strategy (ISRES) is an algorithm for - non-linearly constrained global optimization. It has heuristics to escape local optima, - even though convergence to a global optima is not guaranteed. The evolution strategy is based - on a combination of a mutation rule and differential variation. The fitness ranking is simply - via the objective function for problems without nonlinear constraints. When nonlinear - constraints are included, the `stochastic ranking proposed by Runarsson and Yao - `__ - is employed. This method supports arbitrary nonlinear inequality and equality constraints, in - addition to the bound constraints. - - NLopt global optimizer, derivative-free. - For further detail, please refer to - http://nlopt.readthedocs.io/en/latest/NLopt_Algorithms/#isres-improved-stochastic-ranking-evolution-strategy - """ - - def get_nlopt_optimizer(self) -> NLoptOptimizerType: - """Return NLopt optimizer type""" - return NLoptOptimizerType.GN_ISRES diff --git a/qiskit/algorithms/optimizers/nlopts/nloptimizer.py b/qiskit/algorithms/optimizers/nlopts/nloptimizer.py deleted file mode 100644 index 65f56b930482..000000000000 --- a/qiskit/algorithms/optimizers/nlopts/nloptimizer.py +++ /dev/null @@ -1,131 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Minimize using objective function""" -from __future__ import annotations - -from collections.abc import Callable -from enum import Enum -from abc import abstractmethod -import logging -import numpy as np - -from qiskit.utils import optionals as _optionals -from ..optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT - -logger = logging.getLogger(__name__) - - -class NLoptOptimizerType(Enum): - """NLopt Valid Optimizer""" - - GN_CRS2_LM = 1 - GN_DIRECT_L_RAND = 2 - GN_DIRECT_L = 3 - GN_ESCH = 4 - GN_ISRES = 5 - - -@_optionals.HAS_NLOPT.require_in_instance -class NLoptOptimizer(Optimizer): - """ - NLopt global optimizer base class - """ - - _OPTIONS = ["max_evals"] - - def __init__(self, max_evals: int = 1000) -> None: # pylint: disable=unused-argument - """ - Args: - max_evals: Maximum allowed number of function evaluations. - - Raises: - MissingOptionalLibraryError: NLopt library not installed. - """ - import nlopt - - super().__init__() - for k, v in list(locals().items()): - if k in self._OPTIONS: - self._options[k] = v - - self._optimizer_names = { - NLoptOptimizerType.GN_CRS2_LM: nlopt.GN_CRS2_LM, - NLoptOptimizerType.GN_DIRECT_L_RAND: nlopt.GN_DIRECT_L_RAND, - NLoptOptimizerType.GN_DIRECT_L: nlopt.GN_DIRECT_L, - NLoptOptimizerType.GN_ESCH: nlopt.GN_ESCH, - NLoptOptimizerType.GN_ISRES: nlopt.GN_ISRES, - } - - @abstractmethod - def get_nlopt_optimizer(self) -> NLoptOptimizerType: - """return NLopt optimizer enum type""" - raise NotImplementedError - - def get_support_level(self): - """return support level dictionary""" - return { - "gradient": OptimizerSupportLevel.ignored, - "bounds": OptimizerSupportLevel.supported, - "initial_point": OptimizerSupportLevel.required, - } - - @property - def settings(self): - return {"max_evals": self._options.get("max_evals", 1000)} - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - import nlopt - - x0 = np.asarray(x0) - - if bounds is None: - bounds = [(None, None)] * x0.size - - threshold = 3 * np.pi - low = [(l if l is not None else -threshold) for (l, u) in bounds] - high = [(u if u is not None else threshold) for (l, u) in bounds] - - name = self._optimizer_names[self.get_nlopt_optimizer()] - opt = nlopt.opt(name, len(low)) - logger.debug(opt.get_algorithm_name()) - - opt.set_lower_bounds(low) - opt.set_upper_bounds(high) - - eval_count = 0 - - def wrap_objfunc_global(x, _grad): - nonlocal eval_count - eval_count += 1 - return fun(x) - - opt.set_min_objective(wrap_objfunc_global) - opt.set_maxeval(self._options.get("max_evals", 1000)) - - xopt = opt.optimize(x0) - minf = opt.last_optimum_value() - - logger.debug("Global minimize found %s eval count %s", minf, eval_count) - - result = OptimizerResult() - result.x = xopt - result.fun = minf - result.nfev = eval_count - - return result diff --git a/qiskit/algorithms/optimizers/optimizer.py b/qiskit/algorithms/optimizers/optimizer.py deleted file mode 100644 index e253167b5d9d..000000000000 --- a/qiskit/algorithms/optimizers/optimizer.py +++ /dev/null @@ -1,389 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Optimizer interface""" - -from __future__ import annotations - -from abc import ABC, abstractmethod -from collections.abc import Callable -from enum import IntEnum -import logging -from typing import Any, Union, Protocol - -import numpy as np -import scipy - -from qiskit.algorithms.algorithm_result import AlgorithmResult - -logger = logging.getLogger(__name__) - -POINT = Union[float, np.ndarray] - - -class OptimizerResult(AlgorithmResult): - """The result of an optimization routine.""" - - def __init__(self) -> None: - super().__init__() - self._x: POINT | None = None - self._fun: float | None = None - self._jac: POINT | None = None - self._nfev: int | None = None - self._njev: int | None = None - self._nit: int | None = None - - @property - def x(self) -> POINT | None: - """The final point of the minimization.""" - return self._x - - @x.setter - def x(self, x: POINT | None) -> None: - """Set the final point of the minimization.""" - self._x = x - - @property - def fun(self) -> float | None: - """The final value of the minimization.""" - return self._fun - - @fun.setter - def fun(self, fun: float | None) -> None: - """Set the final value of the minimization.""" - self._fun = fun - - @property - def jac(self) -> POINT | None: - """The final gradient of the minimization.""" - return self._jac - - @jac.setter - def jac(self, jac: POINT | None) -> None: - """Set the final gradient of the minimization.""" - self._jac = jac - - @property - def nfev(self) -> int | None: - """The total number of function evaluations.""" - return self._nfev - - @nfev.setter - def nfev(self, nfev: int | None) -> None: - """Set the total number of function evaluations.""" - self._nfev = nfev - - @property - def njev(self) -> int | None: - """The total number of gradient evaluations.""" - return self._njev - - @njev.setter - def njev(self, njev: int | None) -> None: - """Set the total number of gradient evaluations.""" - self._njev = njev - - @property - def nit(self) -> int | None: - """The total number of iterations.""" - return self._nit - - @nit.setter - def nit(self, nit: int | None) -> None: - """Set the total number of iterations.""" - self._nit = nit - - -class Minimizer(Protocol): - """Callable Protocol for minimizer. - - This interface is based on `SciPy's optimize module - `__. - - This protocol defines a callable taking the following parameters: - - fun - The objective function to minimize (for example the energy in the case of the VQE). - x0 - The initial point for the optimization. - jac - The gradient of the objective function. - bounds - Parameters bounds for the optimization. Note that these might not be supported - by all optimizers. - - and which returns a minimization result object (either SciPy's or Qiskit's). - """ - - # pylint: disable=invalid-name - def __call__( - self, - fun: Callable[[np.ndarray], float], - x0: np.ndarray, - jac: Callable[[np.ndarray], np.ndarray] | None, - bounds: list[tuple[float, float]] | None, - ) -> scipy.optimize.OptimizeResult | OptimizerResult: - """Minimize the objective function. - - This interface is based on `SciPy's optimize module `__. - - Args: - fun: The objective function to minimize (for example the energy in the case of the VQE). - x0: The initial point for the optimization. - jac: The gradient of the objective function. - bounds: Parameters bounds for the optimization. Note that these might not be supported - by all optimizers. - - Returns: - The minimization result object (either SciPy's or Qiskit's). - """ - ... - - -class OptimizerSupportLevel(IntEnum): - """Support Level enum for features such as bounds, gradient and initial point""" - - # pylint: disable=invalid-name - not_supported = 0 # Does not support the corresponding parameter in optimize() - ignored = 1 # Feature can be passed as non None but will be ignored - supported = 2 # Feature is supported - required = 3 # Feature is required and must be given, None is invalid - - -class Optimizer(ABC): - """Base class for optimization algorithm.""" - - @abstractmethod - def __init__(self): - """ - Initialize the optimization algorithm, setting the support - level for _gradient_support_level, _bound_support_level, - _initial_point_support_level, and empty options. - """ - self._gradient_support_level = self.get_support_level()["gradient"] - self._bounds_support_level = self.get_support_level()["bounds"] - self._initial_point_support_level = self.get_support_level()["initial_point"] - self._options = {} - self._max_evals_grouped = None - - @abstractmethod - def get_support_level(self): - """Return support level dictionary""" - raise NotImplementedError - - def set_options(self, **kwargs): - """ - Sets or updates values in the options dictionary. - - The options dictionary may be used internally by a given optimizer to - pass additional optional values for the underlying optimizer/optimization - function used. The options dictionary may be initially populated with - a set of key/values when the given optimizer is constructed. - - Args: - kwargs (dict): options, given as name=value. - """ - for name, value in kwargs.items(): - self._options[name] = value - logger.debug("options: %s", self._options) - - # pylint: disable=invalid-name - @staticmethod - def gradient_num_diff(x_center, f, epsilon, max_evals_grouped=None): - """ - We compute the gradient with the numeric differentiation in the parallel way, - around the point x_center. - - Args: - x_center (ndarray): point around which we compute the gradient - f (func): the function of which the gradient is to be computed. - epsilon (float): the epsilon used in the numeric differentiation. - max_evals_grouped (int): max evals grouped, defaults to 1 (i.e. no batching). - Returns: - grad: the gradient computed - - """ - if max_evals_grouped is None: # no batching by default - max_evals_grouped = 1 - - forig = f(*((x_center,))) - grad = [] - ei = np.zeros((len(x_center),), float) - todos = [] - for k in range(len(x_center)): - ei[k] = 1.0 - d = epsilon * ei - todos.append(x_center + d) - ei[k] = 0.0 - - counter = 0 - chunk = [] - chunks = [] - length = len(todos) - # split all points to chunks, where each chunk has batch_size points - for i in range(length): - x = todos[i] - chunk.append(x) - counter += 1 - # the last one does not have to reach batch_size - if counter == max_evals_grouped or i == length - 1: - chunks.append(chunk) - chunk = [] - counter = 0 - - for chunk in chunks: # eval the chunks in order - parallel_parameters = np.concatenate(chunk) - todos_results = f(parallel_parameters) # eval the points in a chunk (order preserved) - if isinstance(todos_results, float): - grad.append((todos_results - forig) / epsilon) - else: - for todor in todos_results: - grad.append((todor - forig) / epsilon) - - return np.array(grad) - - @staticmethod - def wrap_function(function, args): - """ - Wrap the function to implicitly inject the args at the call of the function. - - Args: - function (func): the target function - args (tuple): the args to be injected - Returns: - function_wrapper: wrapper - """ - - def function_wrapper(*wrapper_args): - return function(*(wrapper_args + args)) - - return function_wrapper - - @property - def setting(self): - """Return setting""" - ret = f"Optimizer: {self.__class__.__name__}\n" - params = "" - for key, value in self.__dict__.items(): - if key[0] == "_": - params += f"-- {key[1:]}: {value}\n" - ret += f"{params}" - return ret - - @property - def settings(self) -> dict[str, Any]: - """The optimizer settings in a dictionary format. - - The settings can for instance be used for JSON-serialization (if all settings are - serializable, which e.g. doesn't hold per default for callables), such that the - optimizer object can be reconstructed as - - .. code-block:: - - settings = optimizer.settings - # JSON serialize and send to another server - optimizer = OptimizerClass(**settings) - - """ - raise NotImplementedError("The settings method is not implemented per default.") - - @abstractmethod - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - """Minimize the scalar function. - - Args: - fun: The scalar function to minimize. - x0: The initial point for the minimization. - jac: The gradient of the scalar function ``fun``. - bounds: Bounds for the variables of ``fun``. This argument might be ignored if the - optimizer does not support bounds. - - Returns: - The result of the optimization, containing e.g. the result as attribute ``x``. - """ - raise NotImplementedError() - - @property - def gradient_support_level(self): - """Returns gradient support level""" - return self._gradient_support_level - - @property - def is_gradient_ignored(self): - """Returns is gradient ignored""" - return self._gradient_support_level == OptimizerSupportLevel.ignored - - @property - def is_gradient_supported(self): - """Returns is gradient supported""" - return self._gradient_support_level != OptimizerSupportLevel.not_supported - - @property - def is_gradient_required(self): - """Returns is gradient required""" - return self._gradient_support_level == OptimizerSupportLevel.required - - @property - def bounds_support_level(self): - """Returns bounds support level""" - return self._bounds_support_level - - @property - def is_bounds_ignored(self): - """Returns is bounds ignored""" - return self._bounds_support_level == OptimizerSupportLevel.ignored - - @property - def is_bounds_supported(self): - """Returns is bounds supported""" - return self._bounds_support_level != OptimizerSupportLevel.not_supported - - @property - def is_bounds_required(self): - """Returns is bounds required""" - return self._bounds_support_level == OptimizerSupportLevel.required - - @property - def initial_point_support_level(self): - """Returns initial point support level""" - return self._initial_point_support_level - - @property - def is_initial_point_ignored(self): - """Returns is initial point ignored""" - return self._initial_point_support_level == OptimizerSupportLevel.ignored - - @property - def is_initial_point_supported(self): - """Returns is initial point supported""" - return self._initial_point_support_level != OptimizerSupportLevel.not_supported - - @property - def is_initial_point_required(self): - """Returns is initial point required""" - return self._initial_point_support_level == OptimizerSupportLevel.required - - def print_options(self): - """Print algorithm-specific options.""" - for name in sorted(self._options): - logger.debug("%s = %s", name, str(self._options[name])) - - def set_max_evals_grouped(self, limit): - """Set max evals grouped""" - self._max_evals_grouped = limit diff --git a/qiskit/algorithms/optimizers/optimizer_utils/__init__.py b/qiskit/algorithms/optimizers/optimizer_utils/__init__.py deleted file mode 100644 index 33c5bc90b087..000000000000 --- a/qiskit/algorithms/optimizers/optimizer_utils/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""Utils for optimizers - -Optimizer Utils (:mod:`qiskit.algorithms.optimizers.optimizer_utils`) -===================================================================== - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - LearningRate - -""" - -from .learning_rate import LearningRate - -__all__ = ["LearningRate"] diff --git a/qiskit/algorithms/optimizers/optimizer_utils/learning_rate.py b/qiskit/algorithms/optimizers/optimizer_utils/learning_rate.py deleted file mode 100644 index 7bfea636ce2c..000000000000 --- a/qiskit/algorithms/optimizers/optimizer_utils/learning_rate.py +++ /dev/null @@ -1,88 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""A class to represent the Learning Rate.""" -from __future__ import annotations - -from collections.abc import Generator, Callable -from itertools import tee -import numpy as np - - -class LearningRate(Generator): - """Represents a Learning Rate. - Will be an attribute of :class:`~.GradientDescentState`. Note that :class:`~.GradientDescent` also - has a learning rate. That learning rate can be a float, a list, an array, a function returning - a generator and will be used to create a generator to be used during the - optimization process. - This class wraps ``Generator`` so that we can also access the last yielded value. - """ - - def __init__( - self, - learning_rate: float - | list[float] - | np.ndarray - | Callable[[], Generator[float, None, None]], - ): - """ - Args: - learning_rate: Used to create a generator to iterate on. - """ - if isinstance(learning_rate, (float, int)): - self._gen = constant(learning_rate) - elif isinstance(learning_rate, Generator): - learning_rate, self._gen = tee(learning_rate) - elif isinstance(learning_rate, (list, np.ndarray)): - self._gen = (eta for eta in learning_rate) - else: - self._gen = learning_rate() - - self._current: float | None = None - - def send(self, value): - """Send a value into the generator. - Return next yielded value or raise StopIteration. - """ - self._current = next(self._gen) - return self.current - - def throw(self, typ, val=None, tb=None): - """Raise an exception in the generator. - Return next yielded value or raise StopIteration. - """ - if val is None: - if tb is None: - raise typ - val = typ() - if tb is not None: - val = val.with_traceback(tb) - raise val - - @property - def current(self): - """Returns the current value of the learning rate.""" - return self._current - - -def constant(learning_rate: float = 0.01) -> Generator[float, None, None]: - """Returns a python generator that always yields the same value. - - Args: - learning_rate: The value to yield. - - Yields: - The learning rate for the next iteration. - """ - - while True: - yield learning_rate diff --git a/qiskit/algorithms/optimizers/p_bfgs.py b/qiskit/algorithms/optimizers/p_bfgs.py deleted file mode 100644 index f166160d98de..000000000000 --- a/qiskit/algorithms/optimizers/p_bfgs.py +++ /dev/null @@ -1,182 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Parallelized Limited-memory BFGS optimizer""" -from __future__ import annotations - -import logging -import multiprocessing -import platform -import warnings -from collections.abc import Callable -from typing import SupportsFloat - -import numpy as np - -from qiskit.utils import algorithm_globals -from qiskit.utils.validation import validate_min - -from .optimizer import OptimizerResult, POINT -from .scipy_optimizer import SciPyOptimizer - -logger = logging.getLogger(__name__) - - -class P_BFGS(SciPyOptimizer): # pylint: disable=invalid-name - """ - Parallelized Limited-memory BFGS optimizer. - - P-BFGS is a parallelized version of :class:`L_BFGS_B` with which it shares the same parameters. - P-BFGS can be useful when the target hardware is a quantum simulator running on a classical - machine. This allows the multiple processes to use simulation to potentially reach a minimum - faster. The parallelization may also help the optimizer avoid getting stuck at local optima. - - Uses scipy.optimize.fmin_l_bfgs_b. - For further detail, please refer to - https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_l_bfgs_b.html - """ - - _OPTIONS = ["maxfun", "ftol", "iprint"] - - # pylint: disable=unused-argument - def __init__( - self, - maxfun: int = 1000, - ftol: SupportsFloat = 10 * np.finfo(float).eps, - iprint: int = -1, - max_processes: int | None = None, - options: dict | None = None, - max_evals_grouped: int = 1, - **kwargs, - ) -> None: - r""" - Args: - maxfun: Maximum number of function evaluations. - ftol: The iteration stops when (f\^k - f\^{k+1})/max{\|f\^k\|,\|f\^{k+1}\|,1} <= ftol. - iprint: Controls the frequency of output. iprint < 0 means no output; - iprint = 0 print only one line at the last iteration; 0 < iprint < 99 - print also f and \|proj g\| every iprint iterations; iprint = 99 print - details of every iteration except n-vectors; iprint = 100 print also the - changes of active set and final x; iprint > 100 print details of - every iteration including x and g. - max_processes: maximum number of processes allowed, has a min. value of 1 if not None. - options: A dictionary of solver options. - max_evals_grouped: Max number of default gradient evaluations performed simultaneously. - kwargs: additional kwargs for scipy.optimize.minimize. - """ - if max_processes: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - validate_min("max_processes", max_processes, 1) - - if options is None: - options = {} - for k, v in list(locals().items()): - if k in self._OPTIONS: - options[k] = v - super().__init__( - method="L-BFGS-B", - options=options, - max_evals_grouped=max_evals_grouped, - **kwargs, - ) - self._max_processes = max_processes - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - x0 = np.asarray(x0) - - num_procs = multiprocessing.cpu_count() - 1 - num_procs = ( - num_procs if self._max_processes is None else min(num_procs, self._max_processes) - ) - num_procs = num_procs if num_procs >= 0 else 0 - - if platform.system() == "Darwin": - # Changed in version 3.8: On macOS, the spawn start method is now the - # default. The fork start method should be considered unsafe as it can - # lead to crashes. - # However P_BFGS doesn't support spawn, so we revert to single process. - num_procs = 0 - logger.warning( - "For MacOS, python >= 3.8, using only current process. " - "Multiple core use not supported." - ) - elif platform.system() == "Windows": - num_procs = 0 - logger.warning( - "For Windows, using only current process. Multiple core use not supported." - ) - - queue: multiprocessing.queues.Queue[tuple[POINT, float, int]] = multiprocessing.Queue() - - # TODO: are automatic bounds a good idea? What if the circuit parameters are not - # just from plain Pauli rotations but have a coefficient? - - # bounds for additional initial points in case bounds has any None values - threshold = 2 * np.pi - if bounds is None: - bounds = [(-threshold, threshold)] * x0.size - low = [(l if l is not None else -threshold) for (l, u) in bounds] - high = [(u if u is not None else threshold) for (l, u) in bounds] - - def optimize_runner(_queue, _i_pt): # Multi-process sampling - _sol, _opt, _nfev = self._optimize(fun, _i_pt, jac, bounds) - _queue.put((_sol, _opt, _nfev)) - - # Start off as many other processes running the optimize (can be 0) - processes = [] - for _ in range(num_procs): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - i_pt = algorithm_globals.random.uniform(low, high) # Another random point in bounds - proc = multiprocessing.Process(target=optimize_runner, args=(queue, i_pt)) - processes.append(proc) - proc.start() - - # While the one optimize in this process below runs the other processes will - # be running too. This one runs - # with the supplied initial point. The process ones have their own random one - sol, opt, nfev = self._optimize(fun, x0, jac, bounds) - - for proc in processes: - # For each other process we wait now for it to finish and see if it has - # a better result than above - proc.join() - p_sol, p_opt, p_nfev = queue.get() - if p_opt < opt: - sol, opt = p_sol, p_opt - nfev += p_nfev - - result = OptimizerResult() - result.x = sol - result.fun = opt - result.nfev = nfev - - return result - - def _optimize( - self, - objective_function, - initial_point, - gradient_function=None, - variable_bounds=None, - ) -> tuple[POINT, float, int]: - result = super().minimize( - objective_function, initial_point, gradient_function, variable_bounds - ) - return result.x, result.fun, result.nfev diff --git a/qiskit/algorithms/optimizers/powell.py b/qiskit/algorithms/optimizers/powell.py deleted file mode 100644 index de8cbb1b9d18..000000000000 --- a/qiskit/algorithms/optimizers/powell.py +++ /dev/null @@ -1,64 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Powell optimizer.""" -from __future__ import annotations - -from .scipy_optimizer import SciPyOptimizer - - -class POWELL(SciPyOptimizer): - """ - Powell optimizer. - - The Powell algorithm performs unconstrained optimization; it ignores bounds or - constraints. Powell is a *conjugate direction method*: it performs sequential one-dimensional - minimization along each directional vector, which is updated at - each iteration of the main minimization loop. The function being minimized need not be - differentiable, and no derivatives are taken. - - Uses scipy.optimize.minimize Powell. - For further detail, please refer to - See https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html - """ - - _OPTIONS = ["maxiter", "maxfev", "disp", "xtol"] - - # pylint: disable=unused-argument - def __init__( - self, - maxiter: int | None = None, - maxfev: int = 1000, - disp: bool = False, - xtol: float = 0.0001, - tol: float | None = None, - options: dict | None = None, - **kwargs, - ) -> None: - """ - Args: - maxiter: Maximum allowed number of iterations. If both maxiter and maxfev - are set, minimization will stop at the first reached. - maxfev: Maximum allowed number of function evaluations. If both maxiter and - maxfev are set, minimization will stop at the first reached. - disp: Set to True to print convergence messages. - xtol: Relative error in solution xopt acceptable for convergence. - tol: Tolerance for termination. - options: A dictionary of solver options. - kwargs: additional kwargs for scipy.optimize.minimize. - """ - if options is None: - options = {} - for k, v in list(locals().items()): - if k in self._OPTIONS: - options[k] = v - super().__init__("Powell", options=options, tol=tol, **kwargs) diff --git a/qiskit/algorithms/optimizers/qnspsa.py b/qiskit/algorithms/optimizers/qnspsa.py deleted file mode 100644 index be5907afbf17..000000000000 --- a/qiskit/algorithms/optimizers/qnspsa.py +++ /dev/null @@ -1,421 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The QN-SPSA optimizer.""" - -from __future__ import annotations - -from collections.abc import Iterator -from typing import Any, Callable - -import numpy as np -from qiskit.providers import Backend -from qiskit.circuit import ParameterVector, QuantumCircuit -from qiskit.opflow import StateFn, CircuitSampler, ExpectationBase -from qiskit.utils import QuantumInstance -from qiskit.utils.deprecation import deprecate_arg - -from qiskit.primitives import BaseSampler, Sampler -from qiskit.algorithms.state_fidelities import ComputeUncompute - -from .spsa import SPSA, CALLBACK, TERMINATIONCHECKER, _batch_evaluate - -# the function to compute the fidelity -FIDELITY = Callable[[np.ndarray, np.ndarray], float] - - -class QNSPSA(SPSA): - r"""The Quantum Natural SPSA (QN-SPSA) optimizer. - - The QN-SPSA optimizer [1] is a stochastic optimizer that belongs to the family of gradient - descent methods. This optimizer is based on SPSA but attempts to improve the convergence by - sampling the **natural gradient** instead of the vanilla, first-order gradient. It achieves - this by approximating Hessian of the ``fidelity`` of the ansatz circuit. - - Compared to natural gradients, which require :math:`\mathcal{O}(d^2)` expectation value - evaluations for a circuit with :math:`d` parameters, QN-SPSA only requires - :math:`\mathcal{O}(1)` and can therefore significantly speed up the natural gradient calculation - by sacrificing some accuracy. Compared to SPSA, QN-SPSA requires 4 additional function - evaluations of the fidelity. - - The stochastic approximation of the natural gradient can be systematically improved by - increasing the number of ``resamplings``. This leads to a Monte Carlo-style convergence to - the exact, analytic value. - - .. note:: - - This component has some function that is normally random. If you want to reproduce behavior - then you should set the random number generator seed in the algorithm_globals - (``qiskit.utils.algorithm_globals.random_seed = seed``). - - Examples: - - This short example runs QN-SPSA for the ground state calculation of the ``Z ^ Z`` - observable where the ansatz is a ``PauliTwoDesign`` circuit. - - .. code-block:: python - - import numpy as np - from qiskit.algorithms.optimizers import QNSPSA - from qiskit.circuit.library import PauliTwoDesign - from qiskit.primitives import Estimator, Sampler - from qiskit.quantum_info import Pauli - - # problem setup - ansatz = PauliTwoDesign(2, reps=1, seed=2) - observable = Pauli("ZZ") - initial_point = np.random.random(ansatz.num_parameters) - - # loss function - estimator = Estimator() - - def loss(x): - result = estimator.run([ansatz], [observable], [x]).result() - return np.real(result.values[0]) - - # fidelity for estimation of the geometric tensor - sampler = Sampler() - fidelity = QNSPSA.get_fidelity(ansatz, sampler) - - # run QN-SPSA - qnspsa = QNSPSA(fidelity, maxiter=300) - result = qnspsa.optimize(ansatz.num_parameters, loss, initial_point=initial_point) - - This is a legacy version solving the same problem but using Qiskit Opflow instead - of the Qiskit Primitives. Note however, that this usage is deprecated. - - .. code-block:: python - - import numpy as np - from qiskit.algorithms.optimizers import QNSPSA - from qiskit.circuit.library import PauliTwoDesign - from qiskit.opflow import Z, StateFn - - ansatz = PauliTwoDesign(2, reps=1, seed=2) - observable = Z ^ Z - initial_point = np.random.random(ansatz.num_parameters) - - def loss(x): - bound = ansatz.assign_parameters(x) - return np.real((StateFn(observable, is_measurement=True) @ StateFn(bound)).eval()) - - fidelity = QNSPSA.get_fidelity(ansatz) - qnspsa = QNSPSA(fidelity, maxiter=300) - result = qnspsa.optimize(ansatz.num_parameters, loss, initial_point=initial_point) - - - References: - - [1] J. Gacon et al, "Simultaneous Perturbation Stochastic Approximation of the Quantum - Fisher Information", `arXiv:2103.09232 `_ - - """ - - def __init__( - self, - fidelity: FIDELITY, - maxiter: int = 100, - blocking: bool = True, - allowed_increase: float | None = None, - learning_rate: float | Callable[[], Iterator] | None = None, - perturbation: float | Callable[[], Iterator] | None = None, - resamplings: int | dict[int, int] = 1, - perturbation_dims: int | None = None, - regularization: float | None = None, - hessian_delay: int = 0, - lse_solver: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, - initial_hessian: np.ndarray | None = None, - callback: CALLBACK | None = None, - termination_checker: TERMINATIONCHECKER | None = None, - ) -> None: - r""" - Args: - fidelity: A function to compute the fidelity of the ansatz state with itself for - two different sets of parameters. - maxiter: The maximum number of iterations. Note that this is not the maximal number - of function evaluations. - blocking: If True, only accepts updates that improve the loss (up to some allowed - increase, see next argument). - allowed_increase: If ``blocking`` is ``True``, this argument determines by how much - the loss can increase with the proposed parameters and still be accepted. - If ``None``, the allowed increases is calibrated automatically to be twice the - approximated standard deviation of the loss function. - learning_rate: The update step is the learning rate is multiplied with the gradient. - If the learning rate is a float, it remains constant over the course of the - optimization. It can also be a callable returning an iterator which yields the - learning rates for each optimization step. - If ``learning_rate`` is set ``perturbation`` must also be provided. - perturbation: Specifies the magnitude of the perturbation for the finite difference - approximation of the gradients. Can be either a float or a generator yielding - the perturbation magnitudes per step. - If ``perturbation`` is set ``learning_rate`` must also be provided. - resamplings: The number of times the gradient (and Hessian) is sampled using a random - direction to construct a gradient estimate. Per default the gradient is estimated - using only one random direction. If an integer, all iterations use the same number - of resamplings. If a dictionary, this is interpreted as - ``{iteration: number of resamplings per iteration}``. - perturbation_dims: The number of perturbed dimensions. Per default, all dimensions - are perturbed, but a smaller, fixed number can be perturbed. If set, the perturbed - dimensions are chosen uniformly at random. - regularization: To ensure the preconditioner is symmetric and positive definite, the - identity times a small coefficient is added to it. This generator yields that - coefficient. - hessian_delay: Start multiplying the gradient with the inverse Hessian only after a - certain number of iterations. The Hessian is still evaluated and therefore this - argument can be useful to first get a stable average over the last iterations before - using it as preconditioner. - lse_solver: The method to solve for the inverse of the Hessian. Per default an - exact LSE solver is used, but can e.g. be overwritten by a minimization routine. - initial_hessian: The initial guess for the Hessian. By default the identity matrix - is used. - callback: A callback function passed information in each iteration step. The - information is, in this order: the parameters, the function value, the number - of function evaluations, the stepsize, whether the step was accepted. - termination_checker: A callback function executed at the end of each iteration step. The - arguments are, in this order: the parameters, the function value, the number - of function evaluations, the stepsize, whether the step was accepted. If the callback - returns True, the optimization is terminated. - To prevent additional evaluations of the objective method, if the objective has not yet - been evaluated, the objective is estimated by taking the mean of the objective - evaluations used in the estimate of the gradient. - - - """ - super().__init__( - maxiter, - blocking, - allowed_increase, - # trust region *must* be false for natural gradients to work - trust_region=False, - learning_rate=learning_rate, - perturbation=perturbation, - resamplings=resamplings, - callback=callback, - second_order=True, - hessian_delay=hessian_delay, - lse_solver=lse_solver, - regularization=regularization, - perturbation_dims=perturbation_dims, - initial_hessian=initial_hessian, - termination_checker=termination_checker, - ) - - self.fidelity = fidelity - - def _point_sample(self, loss, x, eps, delta1, delta2): - loss_points = [x + eps * delta1, x - eps * delta1] - fidelity_points = [ - (x, x + eps * delta1), - (x, x - eps * delta1), - (x, x + eps * (delta1 + delta2)), - (x, x + eps * (-delta1 + delta2)), - ] - self._nfev += 6 - - loss_values = _batch_evaluate(loss, loss_points, self._max_evals_grouped) - fidelity_values = _batch_evaluate( - self.fidelity, fidelity_points, self._max_evals_grouped, unpack_points=True - ) - - # compute the gradient approximation and additionally return the loss function evaluations - gradient_estimate = (loss_values[0] - loss_values[1]) / (2 * eps) * delta1 - - # compute the preconditioner point estimate - fidelity_values = np.asarray(fidelity_values, dtype=float) - diff = fidelity_values[2] - fidelity_values[0] - diff = diff - (fidelity_values[3] - fidelity_values[1]) - diff = diff / (2 * eps**2) - - rank_one = np.outer(delta1, delta2) - # -0.5 factor comes from the fact that we need -0.5 * fidelity - hessian_estimate = -0.5 * diff * (rank_one + rank_one.T) / 2 - - return np.mean(loss_values), gradient_estimate, hessian_estimate - - @property - def settings(self) -> dict[str, Any]: - """The optimizer settings in a dictionary format.""" - # re-use serialization from SPSA - settings = super().settings - settings.update({"fidelity": self.fidelity}) - - # remove SPSA-specific arguments not in QNSPSA - settings.pop("trust_region") - settings.pop("second_order") - - return settings - - @staticmethod - @deprecate_arg( - "backend", - since="0.24.0", - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - # We allow passing a sampler as the second argument because that will become a positional - # argument for `sampler` after removing `backend` and `expectation`. - predicate=lambda backend: not isinstance(backend, BaseSampler), - ) - @deprecate_arg( - "expectation", - since="0.24.0", - additional_msg="See https://qisk.it/algo_migration for a migration guide.", - ) - def get_fidelity( - circuit: QuantumCircuit, - backend: Backend | QuantumInstance | None = None, - expectation: ExpectationBase | None = None, - *, - sampler: BaseSampler | None = None, - ) -> Callable[[np.ndarray, np.ndarray], float]: - r"""Get a function to compute the fidelity of ``circuit`` with itself. - - .. note:: - - Using this function with a backend and expectation converter is pending deprecation, - instead pass a Qiskit Primitive sampler, such as :class:`~.Sampler`. - The sampler can be passed as keyword argument or, positionally, as second argument. - - Let ``circuit`` be a parameterized quantum circuit performing the operation - :math:`U(\theta)` given a set of parameters :math:`\theta`. Then this method returns - a function to evaluate - - .. math:: - - F(\theta, \phi) = \big|\langle 0 | U^\dagger(\theta) U(\phi) |0\rangle \big|^2. - - The output of this function can be used as input for the ``fidelity`` to the - :class:`~.QNSPSA` optimizer. - - Args: - circuit: The circuit preparing the parameterized ansatz. - backend: Deprecated. A backend of quantum instance to evaluate the circuits. - If None, plain matrix multiplication will be used. - expectation: Deprecated. An expectation converter to specify how the expected - value is computed. If a shot-based readout is used this should be set to - ``PauliExpectation``. - sampler: A sampler primitive to sample from a quantum state. - - Returns: - A handle to the function :math:`F`. - - """ - # allow passing sampler by position - if isinstance(backend, BaseSampler): - sampler = backend - backend = None - - if expectation is None and backend is None and sampler is None: - sampler = Sampler() - - if expectation is not None or backend is not None: - return QNSPSA._legacy_get_fidelity(circuit, backend, expectation) - - fid = ComputeUncompute(sampler) - - num_parameters = circuit.num_parameters - - def fidelity(values_x, values_y): - values_x = np.reshape(values_x, (-1, num_parameters)).tolist() - batch_size_x = len(values_x) - - values_y = np.reshape(values_y, (-1, num_parameters)).tolist() - batch_size_y = len(values_y) - - result = fid.run( - batch_size_x * [circuit], batch_size_y * [circuit], values_x, values_y - ).result() - return np.asarray(result.fidelities) - - return fidelity - - @staticmethod - def _legacy_get_fidelity( - circuit: QuantumCircuit, - backend: Backend | QuantumInstance | None = None, - expectation: ExpectationBase | None = None, - ) -> Callable[[np.ndarray, np.ndarray], float]: - r"""Deprecated. Get a function to compute the fidelity of ``circuit`` with itself. - - .. note:: - - This method is deprecated. Instead use the :class:`~.ComputeUncompute` - class which implements the fidelity calculation in the same fashion as this method. - - Let ``circuit`` be a parameterized quantum circuit performing the operation - :math:`U(\theta)` given a set of parameters :math:`\theta`. Then this method returns - a function to evaluate - - .. math:: - - F(\theta, \phi) = \big|\langle 0 | U^\dagger(\theta) U(\phi) |0\rangle \big|^2. - - The output of this function can be used as input for the ``fidelity`` to the - :class:~`qiskit.algorithms.optimizers.QNSPSA` optimizer. - - Args: - circuit: The circuit preparing the parameterized ansatz. - backend: A backend of quantum instance to evaluate the circuits. If None, plain - matrix multiplication will be used. - expectation: An expectation converter to specify how the expected value is computed. - If a shot-based readout is used this should be set to ``PauliExpectation``. - - Returns: - A handle to the function :math:`F`. - - """ - params_x = ParameterVector("x", circuit.num_parameters) - params_y = ParameterVector("y", circuit.num_parameters) - - expression = ~StateFn(circuit.assign_parameters(params_x)) @ StateFn( - circuit.assign_parameters(params_y) - ) - - if expectation is not None: - expression = expectation.convert(expression) - - if backend is None: - - def fidelity(values_x, values_y): - value_dict = dict( - zip(params_x[:] + params_y[:], values_x.tolist() + values_y.tolist()) - ) - return np.abs(expression.assign_parameters(value_dict).eval()) ** 2 - - else: - sampler = CircuitSampler(backend) - - def fidelity(values_x, values_y=None): - # no batches - if isinstance(values_x, np.ndarray) and isinstance(values_y, np.ndarray): - value_dict = dict( - zip(params_x[:] + params_y[:], values_x.tolist() + values_y.tolist()) - ) - # legacy batching -- remove once QNSPSA.get_fidelity is only supported with sampler - elif values_y is None: - value_dict = {p: [] for p in params_x[:] + params_y[:]} - for values_xy in values_x: - for value_x, param_x in zip(values_xy[0, :], params_x): - value_dict[param_x].append(value_x) - - for value_y, param_y in zip(values_xy[1, :], params_y): - value_dict[param_y].append(value_y) - else: - value_dict = {p: [] for p in params_x[:] + params_y[:]} - for values_i_x, values_i_y in zip(values_x, values_y): - for value_x, param_x in zip(values_i_x, params_x): - value_dict[param_x].append(value_x) - - for value_y, param_y in zip(values_i_y, params_y): - value_dict[param_y].append(value_y) - - return np.abs(sampler.convert(expression, params=value_dict).eval()) ** 2 - - return fidelity diff --git a/qiskit/algorithms/optimizers/scipy_optimizer.py b/qiskit/algorithms/optimizers/scipy_optimizer.py deleted file mode 100644 index 3a22b41bcfbd..000000000000 --- a/qiskit/algorithms/optimizers/scipy_optimizer.py +++ /dev/null @@ -1,183 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Wrapper class of scipy.optimize.minimize.""" -from __future__ import annotations - -import warnings -from collections.abc import Callable -from typing import Any - -import numpy as np -from scipy.optimize import minimize - -from qiskit.utils.validation import validate_min - -from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT - - -class SciPyOptimizer(Optimizer): - """A general Qiskit Optimizer wrapping scipy.optimize.minimize. - - For further detail, please refer to - https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html - """ - - _bounds_support_methods = {"l-bfgs-b", "tnc", "slsqp", "powell", "trust-constr"} - _gradient_support_methods = { - "cg", - "bfgs", - "newton-cg", - "l-bfgs-b", - "tnc", - "slsqp", - "dogleg", - "trust-ncg", - "trust-krylov", - "trust-exact", - "trust-constr", - } - - def __init__( - self, - method: str | Callable, - options: dict[str, Any] | None = None, - max_evals_grouped: int = 1, - **kwargs, - ): - """ - Args: - method: Type of solver. - options: A dictionary of solver options. - kwargs: additional kwargs for scipy.optimize.minimize. - max_evals_grouped: Max number of default gradient evaluations performed simultaneously. - """ - self._method = method.lower() if isinstance(method, str) else method - # Set support level - if self._method in self._bounds_support_methods: - self._bounds_support_level = OptimizerSupportLevel.supported - else: - self._bounds_support_level = OptimizerSupportLevel.ignored - if self._method in self._gradient_support_methods: - self._gradient_support_level = OptimizerSupportLevel.supported - else: - self._gradient_support_level = OptimizerSupportLevel.ignored - self._initial_point_support_level = OptimizerSupportLevel.required - - self._options = options if options is not None else {} - - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - validate_min("max_evals_grouped", max_evals_grouped, 1) - - self._max_evals_grouped = max_evals_grouped - self._kwargs = kwargs - - def get_support_level(self): - """Return support level dictionary""" - return { - "gradient": self._gradient_support_level, - "bounds": self._bounds_support_level, - "initial_point": self._initial_point_support_level, - } - - @property - def settings(self) -> dict[str, Any]: - options = self._options.copy() - if hasattr(self, "_OPTIONS"): - # all _OPTIONS should be keys in self._options, but add a failsafe here - attributes = [ - option - for option in self._OPTIONS # pylint: disable=no-member - if option in options.keys() - ] - - settings = {attr: options.pop(attr) for attr in attributes} - else: - settings = {} - - settings["max_evals_grouped"] = self._max_evals_grouped - settings["options"] = options - settings.update(self._kwargs) - - # the subclasses don't need the "method" key as the class type specifies the method - if self.__class__ == SciPyOptimizer: - settings["method"] = self._method - - return settings - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - # Remove ignored parameters to suppress the warning of scipy.optimize.minimize - if self.is_bounds_ignored: - bounds = None - if self.is_gradient_ignored: - jac = None - - if self.is_gradient_supported and jac is None and self._max_evals_grouped > 1: - if "eps" in self._options: - epsilon = self._options["eps"] - else: - epsilon = ( - 1e-8 if self._method in {"l-bfgs-b", "tnc"} else np.sqrt(np.finfo(float).eps) - ) - jac = Optimizer.wrap_function( - Optimizer.gradient_num_diff, (fun, epsilon, self._max_evals_grouped) - ) - - # Workaround for L_BFGS_B because it does not accept np.ndarray. - # See https://github.com/Qiskit/qiskit-terra/pull/6373. - if jac is not None and self._method == "l-bfgs-b": - jac = self._wrap_gradient(jac) - - # Starting in scipy 1.9.0 maxiter is deprecated and maxfun (added in 1.5.0) - # should be used instead - swapped_deprecated_args = False - if self._method == "tnc" and "maxiter" in self._options: - swapped_deprecated_args = True - self._options["maxfun"] = self._options.pop("maxiter") - - raw_result = minimize( - fun=fun, - x0=x0, - method=self._method, - jac=jac, - bounds=bounds, - options=self._options, - **self._kwargs, - ) - if swapped_deprecated_args: - self._options["maxiter"] = self._options.pop("maxfun") - - result = OptimizerResult() - result.x = raw_result.x - result.fun = raw_result.fun - result.nfev = raw_result.nfev - result.njev = raw_result.get("njev", None) - result.nit = raw_result.get("nit", None) - - return result - - @staticmethod - def _wrap_gradient(gradient_function): - def wrapped_gradient(x): - gradient = gradient_function(x) - if isinstance(gradient, np.ndarray): - return gradient.tolist() - return gradient - - return wrapped_gradient diff --git a/qiskit/algorithms/optimizers/slsqp.py b/qiskit/algorithms/optimizers/slsqp.py deleted file mode 100644 index d02eb790afa9..000000000000 --- a/qiskit/algorithms/optimizers/slsqp.py +++ /dev/null @@ -1,73 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Sequential Least SQuares Programming optimizer""" -from __future__ import annotations - - -from .scipy_optimizer import SciPyOptimizer - - -class SLSQP(SciPyOptimizer): - """ - Sequential Least SQuares Programming optimizer. - - SLSQP minimizes a function of several variables with any combination of bounds, equality - and inequality constraints. The method wraps the SLSQP Optimization subroutine originally - implemented by Dieter Kraft. - - SLSQP is ideal for mathematical problems for which the objective function and the constraints - are twice continuously differentiable. Note that the wrapper handles infinite values in bounds - by converting them into large floating values. - - Uses scipy.optimize.minimize SLSQP. - For further detail, please refer to - See https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html - """ - - _OPTIONS = ["maxiter", "disp", "ftol", "eps"] - - # pylint: disable=unused-argument - def __init__( - self, - maxiter: int = 100, - disp: bool = False, - ftol: float = 1e-06, - tol: float | None = None, - eps: float = 1.4901161193847656e-08, - options: dict | None = None, - max_evals_grouped: int = 1, - **kwargs, - ) -> None: - """ - Args: - maxiter: Maximum number of iterations. - disp: Set to True to print convergence messages. - ftol: Precision goal for the value of f in the stopping criterion. - tol: Tolerance for termination. - eps: Step size used for numerical approximation of the Jacobian. - options: A dictionary of solver options. - max_evals_grouped: Max number of default gradient evaluations performed simultaneously. - kwargs: additional kwargs for scipy.optimize.minimize. - """ - if options is None: - options = {} - for k, v in list(locals().items()): - if k in self._OPTIONS: - options[k] = v - super().__init__( - "SLSQP", - options=options, - tol=tol, - max_evals_grouped=max_evals_grouped, - **kwargs, - ) diff --git a/qiskit/algorithms/optimizers/snobfit.py b/qiskit/algorithms/optimizers/snobfit.py deleted file mode 100644 index 8d6a3bde1d07..000000000000 --- a/qiskit/algorithms/optimizers/snobfit.py +++ /dev/null @@ -1,130 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Stable Noisy Optimization by Branch and FIT algorithm (SNOBFIT) optimizer.""" -from __future__ import annotations - -from collections.abc import Callable -from typing import Any - -import numpy as np -from qiskit.exceptions import QiskitError -from qiskit.utils import optionals as _optionals -from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT - - -@_optionals.HAS_SKQUANT.require_in_instance -@_optionals.HAS_SQSNOBFIT.require_in_instance -class SNOBFIT(Optimizer): - """Stable Noisy Optimization by Branch and FIT algorithm. - - SnobFit is used for the optimization of derivative-free, noisy objective functions providing - robust and fast solutions of problems with continuous variables varying within bound. - - Uses skquant.opt installed with pip install scikit-quant. - For further detail, please refer to - https://github.com/scikit-quant/scikit-quant and https://qat4chem.lbl.gov/software. - """ - - def __init__( - self, - maxiter: int = 1000, - maxfail: int = 10, - maxmp: int = None, - verbose: bool = False, - ) -> None: - """ - Args: - maxiter: Maximum number of function evaluations. - maxmp: Maximum number of model points requested for the local fit. - Default = 2 * number of parameters + 6 set to this value when None. - maxfail: Maximum number of failures to improve the solution. Stops the algorithm - after maxfail is reached. - verbose: Provide verbose (debugging) output. - - Raises: - MissingOptionalLibraryError: scikit-quant or SQSnobFit not installed - QiskitError: If NumPy 1.24.0 or above is installed. - See https://github.com/scikit-quant/scikit-quant/issues/24 for more details. - """ - # check version - version = tuple(map(int, np.__version__.split("."))) - if version >= (1, 24, 0): - raise QiskitError( - "SnobFit is incompatible with NumPy 1.24.0 or above, please " - "install a previous version. See also scikit-quant/scikit-quant#24." - ) - - super().__init__() - self._maxiter = maxiter - self._maxfail = maxfail - self._maxmp = maxmp - self._verbose = verbose - - def get_support_level(self): - """Returns support level dictionary.""" - return { - "gradient": OptimizerSupportLevel.ignored, - "bounds": OptimizerSupportLevel.required, - "initial_point": OptimizerSupportLevel.required, - } - - @property - def settings(self) -> dict[str, Any]: - return { - "maxiter": self._maxiter, - "maxfail": self._maxfail, - "maxmp": self._maxmp, - "verbose": self._verbose, - } - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - import skquant.opt as skq - from SQSnobFit import optset - - if bounds is None or any(None in bound_tuple for bound_tuple in bounds): - raise ValueError("Optimizer SNOBFIT requires bounds for all parameters.") - - snobfit_settings = { - "maxmp": self._maxmp, - "maxfail": self._maxfail, - "verbose": self._verbose, - } - options = optset(optin=snobfit_settings) - # counters the error when initial point is outside the acceptable bounds - x0 = np.asarray(x0) - for idx, theta in enumerate(x0): - if abs(theta) > bounds[idx][0]: - x0[idx] = x0[idx] % bounds[idx][0] - elif abs(theta) > bounds[idx][1]: - x0[idx] = x0[idx] % bounds[idx][1] - - res, history = skq.minimize( - fun, - x0, - bounds=bounds, - budget=self._maxiter, - method="snobfit", - options=options, - ) - - optimizer_result = OptimizerResult() - optimizer_result.x = res.optpar - optimizer_result.fun = res.optval - optimizer_result.nfev = len(history) - return optimizer_result diff --git a/qiskit/algorithms/optimizers/spsa.py b/qiskit/algorithms/optimizers/spsa.py deleted file mode 100644 index f69de5baf299..000000000000 --- a/qiskit/algorithms/optimizers/spsa.py +++ /dev/null @@ -1,810 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Simultaneous Perturbation Stochastic Approximation (SPSA) optimizer. - -This implementation allows both, standard first-order as well as second-order SPSA. -""" -from __future__ import annotations - -from collections import deque -from collections.abc import Iterator -from typing import Callable, Any, SupportsFloat -import logging -import warnings -from time import time - -import scipy -import numpy as np - -from qiskit.utils import algorithm_globals -from qiskit.utils.deprecation import deprecate_func - -from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT - -# number of function evaluations, parameters, loss, stepsize, accepted -CALLBACK = Callable[[int, np.ndarray, float, SupportsFloat, bool], None] -TERMINATIONCHECKER = Callable[[int, np.ndarray, float, SupportsFloat, bool], bool] - -logger = logging.getLogger(__name__) - - -class SPSA(Optimizer): - """Simultaneous Perturbation Stochastic Approximation (SPSA) optimizer. - - SPSA [1] is an gradient descent method for optimizing systems with multiple unknown parameters. - As an optimization method, it is appropriately suited to large-scale population models, - adaptive modeling, and simulation optimization. - - .. seealso:: - - Many examples are presented at the `SPSA Web site `__. - - The main feature of SPSA is the stochastic gradient approximation, which requires only two - measurements of the objective function, regardless of the dimension of the optimization - problem. - - Additionally to standard, first-order SPSA, where only gradient information is used, this - implementation also allows second-order SPSA (2-SPSA) [2]. In 2-SPSA we also estimate the - Hessian of the loss with a stochastic approximation and multiply the gradient with the - inverse Hessian to take local curvature into account and improve convergence. - Notably this Hessian estimate requires only a constant number of function evaluations - unlike an exact evaluation of the Hessian, which scales quadratically in the number of - function evaluations. - - .. note:: - - SPSA can be used in the presence of noise, and it is therefore indicated in situations - involving measurement uncertainty on a quantum computation when finding a minimum. - If you are executing a variational algorithm using an OpenQASM - simulator or a real device, SPSA would be the most recommended choice among the optimizers - provided here. - - The optimization process can includes a calibration phase if neither the ``learning_rate`` nor - ``perturbation`` is provided, which requires additional functional evaluations. - (Note that either both or none must be set.) For further details on the automatic calibration, - please refer to the supplementary information section IV. of [3]. - - .. note:: - - This component has some function that is normally random. If you want to reproduce behavior - then you should set the random number generator seed in the algorithm_globals - (``qiskit.utils.algorithm_globals.random_seed = seed``). - - - Examples: - - This short example runs SPSA for the ground state calculation of the ``Z ^ Z`` - observable where the ansatz is a ``PauliTwoDesign`` circuit. - - .. code-block:: python - - import numpy as np - from qiskit.algorithms.optimizers import SPSA - from qiskit.circuit.library import PauliTwoDesign - from qiskit.opflow import Z, StateFn - - ansatz = PauliTwoDesign(2, reps=1, seed=2) - observable = Z ^ Z - initial_point = np.random.random(ansatz.num_parameters) - - def loss(x): - bound = ansatz.assign_parameters(x) - return np.real((StateFn(observable, is_measurement=True) @ StateFn(bound)).eval()) - - spsa = SPSA(maxiter=300) - result = spsa.optimize(ansatz.num_parameters, loss, initial_point=initial_point) - - To use the Hessian information, i.e. 2-SPSA, you can add `second_order=True` to the - initializer of the `SPSA` class, the rest of the code remains the same. - - .. code-block:: python - - two_spsa = SPSA(maxiter=300, second_order=True) - result = two_spsa.optimize(ansatz.num_parameters, loss, initial_point=initial_point) - - The `termination_checker` can be used to implement a custom termination criterion. - - .. code-block:: python - - import numpy as np - from qiskit.algorithms.optimizers import SPSA - - def objective(x): - return np.linalg.norm(x) + .04*np.random.rand(1) - - class TerminationChecker: - - def __init__(self, N : int): - self.N = N - self.values = [] - - def __call__(self, nfev, parameters, value, stepsize, accepted) -> bool: - self.values.append(value) - - if len(self.values) > self.N: - last_values = self.values[-self.N:] - pp = np.polyfit(range(self.N), last_values, 1) - slope = pp[0] / self.N - - if slope > 0: - return True - return False - - spsa = SPSA(maxiter=200, termination_checker=TerminationChecker(10)) - parameters, value, niter = spsa.optimize(2, objective, initial_point=[0.5, 0.5]) - print(f'SPSA completed after {niter} iterations') - - - References: - - [1]: J. C. Spall (1998). An Overview of the Simultaneous Perturbation Method for Efficient - Optimization, Johns Hopkins APL Technical Digest, 19(4), 482–492. - `Online at jhuapl.edu. `_ - - [2]: J. C. Spall (1997). Accelerated second-order stochastic optimization using only - function measurements, Proceedings of the 36th IEEE Conference on Decision and Control, - 1417-1424 vol.2. `Online at IEEE.org. `_ - - [3]: A. Kandala et al. (2017). Hardware-efficient Variational Quantum Eigensolver for - Small Molecules and Quantum Magnets. Nature 549, pages242–246(2017). - `arXiv:1704.05018v2 `_ - - """ - - def __init__( - self, - maxiter: int = 100, - blocking: bool = False, - allowed_increase: float | None = None, - trust_region: bool = False, - learning_rate: float | np.ndarray | Callable[[], Iterator] | None = None, - perturbation: float | np.ndarray | Callable[[], Iterator] | None = None, - last_avg: int = 1, - resamplings: int | dict[int, int] = 1, - perturbation_dims: int | None = None, - second_order: bool = False, - regularization: float | None = None, - hessian_delay: int = 0, - lse_solver: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, - initial_hessian: np.ndarray | None = None, - callback: CALLBACK | None = None, - termination_checker: TERMINATIONCHECKER | None = None, - ) -> None: - r""" - Args: - maxiter: The maximum number of iterations. Note that this is not the maximal number - of function evaluations. - blocking: If True, only accepts updates that improve the loss (up to some allowed - increase, see next argument). - allowed_increase: If ``blocking`` is ``True``, this argument determines by how much - the loss can increase with the proposed parameters and still be accepted. - If ``None``, the allowed increases is calibrated automatically to be twice the - approximated standard deviation of the loss function. - trust_region: If ``True``, restricts the norm of the update step to be :math:`\leq 1`. - learning_rate: The update step is the learning rate is multiplied with the gradient. - If the learning rate is a float, it remains constant over the course of the - optimization. If a NumPy array, the :math:`i`-th element is the learning rate for - the :math:`i`-th iteration. It can also be a callable returning an iterator which - yields the learning rates for each optimization step. - If ``learning_rate`` is set ``perturbation`` must also be provided. - perturbation: Specifies the magnitude of the perturbation for the finite difference - approximation of the gradients. See ``learning_rate`` for the supported types. - If ``perturbation`` is set ``learning_rate`` must also be provided. - last_avg: Return the average of the ``last_avg`` parameters instead of just the - last parameter values. - resamplings: The number of times the gradient (and Hessian) is sampled using a random - direction to construct a gradient estimate. Per default the gradient is estimated - using only one random direction. If an integer, all iterations use the same number - of resamplings. If a dictionary, this is interpreted as - ``{iteration: number of resamplings per iteration}``. - perturbation_dims: The number of perturbed dimensions. Per default, all dimensions - are perturbed, but a smaller, fixed number can be perturbed. If set, the perturbed - dimensions are chosen uniformly at random. - second_order: If True, use 2-SPSA instead of SPSA. In 2-SPSA, the Hessian is estimated - additionally to the gradient, and the gradient is preconditioned with the inverse - of the Hessian to improve convergence. - regularization: To ensure the preconditioner is symmetric and positive definite, the - identity times a small coefficient is added to it. This generator yields that - coefficient. - hessian_delay: Start multiplying the gradient with the inverse Hessian only after a - certain number of iterations. The Hessian is still evaluated and therefore this - argument can be useful to first get a stable average over the last iterations before - using it as preconditioner. - lse_solver: The method to solve for the inverse of the Hessian. Per default an - exact LSE solver is used, but can e.g. be overwritten by a minimization routine. - initial_hessian: The initial guess for the Hessian. By default the identity matrix - is used. - callback: A callback function passed information in each iteration step. The - information is, in this order: the number of function evaluations, the parameters, - the function value, the stepsize, whether the step was accepted. - termination_checker: A callback function executed at the end of each iteration step. The - arguments are, in this order: the parameters, the function value, the number - of function evaluations, the stepsize, whether the step was accepted. If the callback - returns True, the optimization is terminated. - To prevent additional evaluations of the objective method, if the objective has not yet - been evaluated, the objective is estimated by taking the mean of the objective - evaluations used in the estimate of the gradient. - - - Raises: - ValueError: If ``learning_rate`` or ``perturbation`` is an array with less elements - than the number of iterations. - - - """ - super().__init__() - - # general optimizer arguments - self.maxiter = maxiter - self.trust_region = trust_region - self.callback = callback - self.termination_checker = termination_checker - - # if learning rate and perturbation are arrays, check they are sufficiently long - for attr, name in zip([learning_rate, perturbation], ["learning_rate", "perturbation"]): - if isinstance(attr, (list, np.ndarray)): - if len(attr) < maxiter: - raise ValueError(f"Length of {name} is smaller than maxiter ({maxiter}).") - - self.learning_rate = learning_rate - self.perturbation = perturbation - - # SPSA specific arguments - self.blocking = blocking - self.allowed_increase = allowed_increase - self.last_avg = last_avg - self.resamplings = resamplings - self.perturbation_dims = perturbation_dims - - # 2-SPSA specific arguments - if regularization is None: - regularization = 0.01 - - self.second_order = second_order - self.hessian_delay = hessian_delay - self.lse_solver = lse_solver - self.regularization = regularization - self.initial_hessian = initial_hessian - - # runtime arguments - self._nfev: int | None = None # the number of function evaluations - self._smoothed_hessian: np.ndarray | None = None # smoothed average of the Hessians - - @staticmethod - def calibrate( - loss: Callable[[np.ndarray], float], - initial_point: np.ndarray, - c: float = 0.2, - stability_constant: float = 0, - target_magnitude: float | None = None, # 2 pi / 10 - alpha: float = 0.602, - gamma: float = 0.101, - modelspace: bool = False, - max_evals_grouped: int = 1, - ) -> tuple[Callable, Callable]: - r"""Calibrate SPSA parameters with a powerseries as learning rate and perturbation coeffs. - - The powerseries are: - - .. math:: - - a_k = \frac{a}{(A + k + 1)^\alpha}, c_k = \frac{c}{(k + 1)^\gamma} - - Args: - loss: The loss function. - initial_point: The initial guess of the iteration. - c: The initial perturbation magnitude. - stability_constant: The value of `A`. - target_magnitude: The target magnitude for the first update step, defaults to - :math:`2\pi / 10`. - alpha: The exponent of the learning rate powerseries. - gamma: The exponent of the perturbation powerseries. - modelspace: Whether the target magnitude is the difference of parameter values - or function values (= model space). - max_evals_grouped: The number of grouped evaluations supported by the loss function. - Defaults to 1, i.e. no grouping. - - Returns: - tuple(generator, generator): A tuple of powerseries generators, the first one for the - learning rate and the second one for the perturbation. - """ - logger.info("SPSA: Starting calibration of learning rate and perturbation.") - if target_magnitude is None: - target_magnitude = 2 * np.pi / 10 - - dim = len(initial_point) - - # compute the average magnitude of the first step - steps = 25 - points = [] - for _ in range(steps): - # compute the random direction - pert = bernoulli_perturbation(dim) - points += [initial_point + c * pert, initial_point - c * pert] - - losses = _batch_evaluate(loss, points, max_evals_grouped) - - avg_magnitudes = 0.0 - for i in range(steps): - delta = losses[2 * i] - losses[2 * i + 1] - avg_magnitudes += np.abs(delta / (2 * c)) - - avg_magnitudes /= steps - - if modelspace: - a = target_magnitude / (avg_magnitudes**2) - else: - a = target_magnitude / avg_magnitudes - - # compute the rescaling factor for correct first learning rate - if a < 1e-10: - warnings.warn(f"Calibration failed, using {target_magnitude} for `a`") - a = target_magnitude - - logger.info("Finished calibration:") - logger.info( - " -- Learning rate: a / ((A + n) ^ alpha) with a = %s, A = %s, alpha = %s", - a, - stability_constant, - alpha, - ) - logger.info(" -- Perturbation: c / (n ^ gamma) with c = %s, gamma = %s", c, gamma) - - # set up the powerseries - def learning_rate(): - return powerseries(a, alpha, stability_constant) - - def perturbation(): - return powerseries(c, gamma) - - return learning_rate, perturbation - - @staticmethod - def estimate_stddev( - loss: Callable[[np.ndarray], float], - initial_point: np.ndarray, - avg: int = 25, - max_evals_grouped: int = 1, - ) -> float: - """Estimate the standard deviation of the loss function.""" - losses = _batch_evaluate(loss, avg * [initial_point], max_evals_grouped) - return np.std(losses) - - @property - def settings(self) -> dict[str, Any]: - # if learning rate or perturbation are custom iterators expand them - if callable(self.learning_rate): - iterator = self.learning_rate() - learning_rate = np.array([next(iterator) for _ in range(self.maxiter)]) - else: - learning_rate = self.learning_rate - - if callable(self.perturbation): - iterator = self.perturbation() - perturbation = np.array([next(iterator) for _ in range(self.maxiter)]) - else: - perturbation = self.perturbation - - return { - "maxiter": self.maxiter, - "learning_rate": learning_rate, - "perturbation": perturbation, - "trust_region": self.trust_region, - "blocking": self.blocking, - "allowed_increase": self.allowed_increase, - "resamplings": self.resamplings, - "perturbation_dims": self.perturbation_dims, - "second_order": self.second_order, - "hessian_delay": self.hessian_delay, - "regularization": self.regularization, - "lse_solver": self.lse_solver, - "initial_hessian": self.initial_hessian, - "callback": self.callback, - "termination_checker": self.termination_checker, - } - - def _point_sample(self, loss, x, eps, delta1, delta2): - """A single sample of the gradient at position ``x`` in direction ``delta``.""" - # points to evaluate - points = [x + eps * delta1, x - eps * delta1] - self._nfev += 2 - - if self.second_order: - points += [x + eps * (delta1 + delta2), x + eps * (-delta1 + delta2)] - self._nfev += 2 - - # batch evaluate the points (if possible) - values = _batch_evaluate(loss, points, self._max_evals_grouped) - - plus = values[0] - minus = values[1] - gradient_sample = (plus - minus) / (2 * eps) * delta1 - - hessian_sample = None - if self.second_order: - diff = (values[2] - plus) - (values[3] - minus) - diff /= 2 * eps**2 - - rank_one = np.outer(delta1, delta2) - hessian_sample = diff * (rank_one + rank_one.T) / 2 - - return np.mean(values), gradient_sample, hessian_sample - - def _point_estimate(self, loss, x, eps, num_samples): - """The gradient estimate at point x.""" - # set up variables to store averages - value_estimate = 0 - gradient_estimate = np.zeros(x.size) - hessian_estimate = np.zeros((x.size, x.size)) - - # iterate over the directions - deltas1 = [ - bernoulli_perturbation(x.size, self.perturbation_dims) for _ in range(num_samples) - ] - - if self.second_order: - deltas2 = [ - bernoulli_perturbation(x.size, self.perturbation_dims) for _ in range(num_samples) - ] - else: - deltas2 = None - - for i in range(num_samples): - delta1 = deltas1[i] - delta2 = deltas2[i] if self.second_order else None - - value_sample, gradient_sample, hessian_sample = self._point_sample( - loss, x, eps, delta1, delta2 - ) - value_estimate += value_sample - gradient_estimate += gradient_sample - - if self.second_order: - hessian_estimate += hessian_sample - - return ( - value_estimate / num_samples, - gradient_estimate / num_samples, - hessian_estimate / num_samples, - ) - - def _compute_update(self, loss, x, k, eps, lse_solver): - # compute the perturbations - if isinstance(self.resamplings, dict): - num_samples = self.resamplings.get(k, 1) - else: - num_samples = self.resamplings - - # accumulate the number of samples - value, gradient, hessian = self._point_estimate(loss, x, eps, num_samples) - - # precondition gradient with inverse Hessian, if specified - if self.second_order: - smoothed = k / (k + 1) * self._smoothed_hessian + 1 / (k + 1) * hessian - self._smoothed_hessian = smoothed - - if k > self.hessian_delay: - spd_hessian = _make_spd(smoothed, self.regularization) - - # solve for the gradient update - gradient = np.real(lse_solver(spd_hessian, gradient)) - - return value, gradient - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - # ensure learning rate and perturbation are correctly set: either none or both - # this happens only here because for the calibration the loss function is required - if self.learning_rate is None and self.perturbation is None: - get_eta, get_eps = self.calibrate(fun, x0, max_evals_grouped=self._max_evals_grouped) - else: - get_eta, get_eps = _validate_pert_and_learningrate( - self.perturbation, self.learning_rate - ) - eta, eps = get_eta(), get_eps() - - if self.lse_solver is None: - lse_solver = np.linalg.solve - else: - lse_solver = self.lse_solver - - # prepare some initials - x = np.asarray(x0) - if self.initial_hessian is None: - self._smoothed_hessian = np.identity(x.size) - else: - self._smoothed_hessian = self.initial_hessian - - self._nfev = 0 - - # if blocking is enabled we need to keep track of the function values - if self.blocking: - fx = fun(x) - - self._nfev += 1 - if self.allowed_increase is None: - self.allowed_increase = 2 * self.estimate_stddev( - fun, x, max_evals_grouped=self._max_evals_grouped - ) - - logger.info("SPSA: Starting optimization.") - start = time() - - # keep track of the last few steps to return their average - last_steps = deque([x]) - - # use a local variable and while loop to keep track of the number of iterations - # if the termination checker terminates early - k = 0 - while k < self.maxiter: - k += 1 - iteration_start = time() - # compute update - fx_estimate, update = self._compute_update(fun, x, k, next(eps), lse_solver) - - # trust region - if self.trust_region: - norm = np.linalg.norm(update) - if norm > 1: # stop from dividing by 0 - update = update / norm - - # compute next parameter value - update = update * next(eta) - x_next = x - update - fx_next = None - - # blocking - if self.blocking: - self._nfev += 1 - fx_next = fun(x_next) - - if fx + self.allowed_increase <= fx_next: # accept only if loss improved - if self.callback is not None: - self.callback( - self._nfev, # number of function evals - x_next, # next parameters - fx_next, # loss at next parameters - np.linalg.norm(update), # size of the update step - False, - ) # not accepted - - logger.info( - "Iteration %s/%s rejected in %s.", - k, - self.maxiter + 1, - time() - iteration_start, - ) - continue - fx = fx_next - - logger.info( - "Iteration %s/%s done in %s.", k, self.maxiter + 1, time() - iteration_start - ) - - if self.callback is not None: - # if we didn't evaluate the function yet, do it now - if not self.blocking: - self._nfev += 1 - fx_next = fun(x_next) - - self.callback( - self._nfev, # number of function evals - x_next, # next parameters - fx_next, # loss at next parameters - np.linalg.norm(update), # size of the update step - True, - ) # accepted - - # update parameters - x = x_next - - # update the list of the last ``last_avg`` parameters - if self.last_avg > 1: - last_steps.append(x_next) - if len(last_steps) > self.last_avg: - last_steps.popleft() - - if self.termination_checker is not None: - fx_check = fx_estimate if fx_next is None else fx_next - if self.termination_checker( - self._nfev, x_next, fx_check, np.linalg.norm(update), True - ): - logger.info("terminated optimization at {k}/{self.maxiter} iterations") - break - - logger.info("SPSA: Finished in %s", time() - start) - - if self.last_avg > 1: - x = np.mean(last_steps, axis=0) - - result = OptimizerResult() - result.x = x - result.fun = fun(x) - result.nfev = self._nfev - result.nit = k - - return result - - def get_support_level(self): - """Get the support level dictionary.""" - return { - "gradient": OptimizerSupportLevel.ignored, - "bounds": OptimizerSupportLevel.ignored, - "initial_point": OptimizerSupportLevel.required, - } - - # pylint: disable=bad-docstring-quotes - @deprecate_func( - additional_msg=( - "Instead, use ``SPSA.minimize`` as a replacement, which supports the same arguments " - "but follows the interface of scipy.optimize and returns a complete result object " - "containing additional information." - ), - since="0.21.0", - ) - def optimize( - self, - num_vars, # pylint: disable=unused-argument - objective_function, - gradient_function=None, # pylint: disable=unused-argument - variable_bounds=None, # pylint: disable=unused-argument - initial_point=None, - ): - """Perform optimization. - - Args: - num_vars (int): Number of parameters to be optimized. - objective_function (callable): A function that computes the objective function. - gradient_function (callable): Not supported for SPSA. - variable_bounds (list[(float, float)]): Not supported for SPSA. - initial_point (numpy.ndarray[float]): Initial point. - - Returns: - tuple: point, value, nfev - point: is a 1D numpy.ndarray[float] containing the solution - value: is a float with the objective function value - nfev: number of objective function calls made if available or None - """ - result = self.minimize(objective_function, initial_point) - return result.x, result.fun, result.nfev - - -def bernoulli_perturbation(dim, perturbation_dims=None): - """Get a Bernoulli random perturbation.""" - if perturbation_dims is None: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - return 1 - 2 * algorithm_globals.random.binomial(1, 0.5, size=dim) - - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - pert = 1 - 2 * algorithm_globals.random.binomial(1, 0.5, size=perturbation_dims) - indices = algorithm_globals.random.choice( - list(range(dim)), size=perturbation_dims, replace=False - ) - result = np.zeros(dim) - result[indices] = pert - - return result - - -def powerseries(eta=0.01, power=2, offset=0): - """Yield a series decreasing by a powerlaw.""" - - n = 1 - while True: - yield eta / ((n + offset) ** power) - n += 1 - - -def constant(eta=0.01): - """Yield a constant series.""" - - while True: - yield eta - - -def _batch_evaluate(function, points, max_evals_grouped, unpack_points=False): - """Evaluate a function on all points with batches of max_evals_grouped. - - The points are a list of inputs, as ``[in1, in2, in3, ...]``. If the individual - inputs are tuples (because the function takes multiple inputs), set ``unpack_points`` to ``True``. - """ - - # if the function cannot handle lists of points as input, cover this case immediately - if max_evals_grouped is None or max_evals_grouped == 1: - # support functions with multiple arguments where the points are given in a tuple - return [ - function(*point) if isinstance(point, tuple) else function(point) for point in points - ] - - num_points = len(points) - - # get the number of batches - num_batches = num_points // max_evals_grouped - if num_points % max_evals_grouped != 0: - num_batches += 1 - - # split the points - batched_points = np.array_split(np.asarray(points), num_batches) - - results = [] - for batch in batched_points: - if unpack_points: - batch = _repack_points(batch) - results += _as_list(function(*batch)) - else: - results += _as_list(function(batch)) - - return results - - -def _as_list(obj): - """Convert a list or numpy array into a list.""" - return obj.tolist() if isinstance(obj, np.ndarray) else obj - - -def _repack_points(points): - """Turn a list of tuples of points into a tuple of lists of points. - E.g. turns - [(a1, a2, a3), (b1, b2, b3)] - into - ([a1, b1], [a2, b2], [a3, b3]) - where all elements are np.ndarray. - """ - num_sets = len(points[0]) # length of (a1, a2, a3) - return ([x[i] for x in points] for i in range(num_sets)) - - -def _make_spd(matrix, bias=0.01): - identity = np.identity(matrix.shape[0]) - psd = scipy.linalg.sqrtm(matrix.dot(matrix)) - return psd + bias * identity - - -def _validate_pert_and_learningrate(perturbation, learning_rate): - if learning_rate is None or perturbation is None: - raise ValueError("If one of learning rate or perturbation is set, both must be set.") - - if isinstance(perturbation, float): - - def get_eps(): - return constant(perturbation) - - elif isinstance(perturbation, (list, np.ndarray)): - - def get_eps(): - return iter(perturbation) - - else: - get_eps = perturbation - - if isinstance(learning_rate, float): - - def get_eta(): - return constant(learning_rate) - - elif isinstance(learning_rate, (list, np.ndarray)): - - def get_eta(): - return iter(learning_rate) - - else: - get_eta = learning_rate - - return get_eta, get_eps diff --git a/qiskit/algorithms/optimizers/steppable_optimizer.py b/qiskit/algorithms/optimizers/steppable_optimizer.py deleted file mode 100644 index ed9c75d86b04..000000000000 --- a/qiskit/algorithms/optimizers/steppable_optimizer.py +++ /dev/null @@ -1,303 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""SteppableOptimizer interface""" -from __future__ import annotations - -from abc import abstractmethod, ABC -from collections.abc import Callable -from dataclasses import dataclass -from .optimizer import Optimizer, POINT, OptimizerResult - - -@dataclass -class AskData(ABC): - """Base class for return type of :meth:`~.SteppableOptimizer.ask`. - - Args: - x_fun: Point or list of points where the function needs to be evaluated to compute the next - state of the optimizer. - x_jac: Point or list of points where the gradient/jacobian needs to be evaluated to compute - the next state of the optimizer. - - """ - - x_fun: POINT | list[POINT] | None = None - x_jac: POINT | list[POINT] | None = None - - -@dataclass -class TellData(ABC): - """Base class for argument type of :meth:`~.SteppableOptimizer.tell`. - - Args: - eval_fun: Image of the function at :attr:`~.ask_data.x_fun`. - eval_jac: Image of the gradient-jacobian at :attr:`~.ask_data.x_jac`. - - """ - - eval_fun: float | list[float] | None = None - eval_jac: POINT | list[POINT] | None = None - - -@dataclass -class OptimizerState: - """Base class representing the state of the optimizer. - - This class stores the current state of the optimizer, given by the current point and - (optionally) information like the function value, the gradient or the number of - function evaluations. This dataclass can also store any other individual variables that - change during the optimization. - - """ - - x: POINT - """Current optimization parameters.""" - fun: Callable[[POINT], float] | None - """Function being optimized.""" - jac: Callable[[POINT], POINT] | None - """Jacobian of the function being optimized.""" - nfev: int | None - """Number of function evaluations so far in the optimization.""" - njev: int | None - """Number of jacobian evaluations so far in the opimization.""" - nit: int | None - """Number of optimization steps performed so far in the optimization.""" - - -class SteppableOptimizer(Optimizer): - """ - Base class for a steppable optimizer. - - This family of optimizers uses the `ask and tell interface - `_. - When using this interface the user has to call :meth:`~.ask` to get information about - how to evaluate the function (we are asking the optimizer about how to do the evaluation). - This information is typically the next points at which the function is evaluated, but depending - on the optimizer it can also determine whether to evaluate the function or its gradient. - Once the function has been evaluated, the user calls the method :meth:`~..tell` - to tell the optimizer what the result of the function evaluation(s) is. The optimizer then - updates its state accordingly and the user can decide whether to stop the optimization process - or to repeat a step. - - This interface is more customizable, and allows the user to have full control over the evaluation - of the function. - - Examples: - - An example where the evaluation of the function has a chance of failing. The user, with - specific knowledge about his function can catch this errors and handle them before passing - the result to the optimizer. - - .. code-block:: python - - import random - import numpy as np - from qiskit.algorithms.optimizers import GradientDescent - - def objective(x): - if random.choice([True, False]): - return None - else: - return (np.linalg.norm(x) - 1) ** 2 - - def grad(x): - if random.choice([True, False]): - return None - else: - return 2 * (np.linalg.norm(x) - 1) * x / np.linalg.norm(x) - - - initial_point = np.random.normal(0, 1, size=(100,)) - - optimizer = GradientDescent(maxiter=20) - optimizer.start(x0=initial_point, fun=objective, jac=grad) - - while optimizer.continue_condition(): - ask_data = optimizer.ask() - evaluated_gradient = None - - while evaluated_gradient is None: - evaluated_gradient = grad(ask_data.x_center) - optimizer.state.njev += 1 - - optmizer.state.nit += 1 - - cf = TellData(eval_jac=evaluated_gradient) - optimizer.tell(ask_data=ask_data, tell_data=tell_data) - - result = optimizer.create_result() - - - Users that aren't dealing with complicated functions and who are more familiar with step by step - optimization algorithms can use the :meth:`~.step` method which wraps the :meth:`~.ask` - and :meth:`~.tell` methods. In the same spirit the method :meth:`~.minimize` will optimize the - function and return the result. - - To see other libraries that use this interface one can visit: - https://optuna.readthedocs.io/en/stable/tutorial/20_recipes/009_ask_and_tell.html - - - """ - - def __init__( - self, - maxiter: int = 100, - ): - """ - Args: - maxiter: Number of steps in the optimization process before ending the loop. - """ - super().__init__() - self._state: OptimizerState | None = None - self.maxiter = maxiter - - @property - def state(self) -> OptimizerState: - """Return the current state of the optimizer.""" - return self._state - - @state.setter - def state(self, state: OptimizerState) -> None: - """Set the current state of the optimizer.""" - self._state = state - - def ask(self) -> AskData: - """Ask the optimizer for a set of points to evaluate. - - This method asks the optimizer which are the next points to evaluate. - These points can, e.g., correspond to function values and/or its derivative. - It may also correspond to variables that let the user infer which points to evaluate. - It is the first method inside of a :meth:`~.step` in the optimization process. - - Returns: - An object containing the data needed to make the function evaluation to advance the - optimization process. - - """ - raise NotImplementedError - - def tell(self, ask_data: AskData, tell_data: TellData) -> None: - """Updates the optimization state using the results of the function evaluation. - - A canonical optimization example using :meth:`~.ask` and :meth:`~.tell` can be seen - in :meth:`~.step`. - - Args: - ask_data: Contains the information on how the evaluation was done. - tell_data: Contains all relevant information about the evaluation of the objective - function. - """ - raise NotImplementedError - - @abstractmethod - def evaluate(self, ask_data: AskData) -> TellData: - """Evaluates the function according to the instructions contained in :attr:`~.ask_data`. - - If the user decides to use :meth:`~.step` instead of :meth:`~.ask` and :meth:`~.tell` - this function will contain the logic on how to evaluate the function. - - Args: - ask_data: Contains the information on how to do the evaluation. - - Returns: - Data of all relevant information about the function evaluation. - - """ - raise NotImplementedError - - def _callback_wrapper(self) -> None: - """ - Wraps the callback function to accommodate each optimizer. - """ - pass - - def step(self) -> None: - """Performs one step in the optimization process. - - This method composes :meth:`~.ask`, :meth:`~.evaluate`, and :meth:`~.tell` to make a "step" - in the optimization process. - """ - ask_data = self.ask() - tell_data = self.evaluate(ask_data=ask_data) - self.tell(ask_data=ask_data, tell_data=tell_data) - - # pylint: disable=invalid-name - @abstractmethod - def start( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> None: - """Populates the state of the optimizer with the data provided and sets all the counters to 0. - - Args: - fun: Function to minimize. - x0: Initial point. - jac: Function to compute the gradient. - bounds: Bounds of the search space. - - """ - raise NotImplementedError - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - """Minimizes the function. - - For well behaved functions the user can call this method to minimize a function. - If the user wants more control on how to evaluate the function a custom loop can be - created using :meth:`~.ask` and :meth:`~.tell` and evaluating the function manually. - - Args: - fun: Function to minimize. - x0: Initial point. - jac: Function to compute the gradient. - bounds: Bounds of the search space. - - Returns: - Object containing the result of the optimization. - - """ - self.start(x0=x0, fun=fun, jac=jac, bounds=bounds) - while self.continue_condition(): - self.step() - self._callback_wrapper() - return self.create_result() - - @abstractmethod - def create_result(self) -> OptimizerResult: - """Returns the result of the optimization. - - All the information needed to create such a result should be stored in the optimizer state - and will typically contain the best point found, the function value and gradient at that point, - the number of function and gradient evaluation and the number of iterations in the optimization. - - Returns: - The result of the optimization process. - - """ - raise NotImplementedError - - def continue_condition(self) -> bool: - """Condition that indicates the optimization process should continue. - - Returns: - ``True`` if the optimization process should continue, ``False`` otherwise. - """ - return self.state.nit < self.maxiter diff --git a/qiskit/algorithms/optimizers/tnc.py b/qiskit/algorithms/optimizers/tnc.py deleted file mode 100644 index 06174e51ace9..000000000000 --- a/qiskit/algorithms/optimizers/tnc.py +++ /dev/null @@ -1,83 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Truncated Newton (TNC) optimizer.""" -from __future__ import annotations - - -from .scipy_optimizer import SciPyOptimizer - - -class TNC(SciPyOptimizer): - """ - Truncated Newton (TNC) optimizer. - - TNC uses a truncated Newton algorithm to minimize a function with variables subject to bounds. - This algorithm uses gradient information; it is also called Newton Conjugate-Gradient. - It differs from the :class:`CG` method as it wraps a C implementation and allows each variable - to be given upper and lower bounds. - - Uses scipy.optimize.minimize TNC - For further detail, please refer to - See https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html - """ - - _OPTIONS = ["maxiter", "disp", "accuracy", "ftol", "xtol", "gtol", "eps"] - - # pylint: disable=unused-argument - def __init__( - self, - maxiter: int = 100, - disp: bool = False, - accuracy: float = 0, - ftol: float = -1, - xtol: float = -1, - gtol: float = -1, - tol: float | None = None, - eps: float = 1e-08, - options: dict | None = None, - max_evals_grouped: int = 1, - **kwargs, - ) -> None: - """ - Args: - maxiter: Maximum number of function evaluation. - disp: Set to True to print convergence messages. - accuracy: Relative precision for finite difference calculations. - If <= machine_precision, set to sqrt(machine_precision). Defaults to 0. - ftol: Precision goal for the value of f in the stopping criterion. - If ftol < 0.0, ftol is set to 0.0 defaults to -1. - xtol: Precision goal for the value of x in the stopping criterion - (after applying x scaling factors). - If xtol < 0.0, xtol is set to sqrt(machine_precision). Defaults to -1. - gtol: Precision goal for the value of the projected gradient in - the stopping criterion (after applying x scaling factors). - If gtol < 0.0, gtol is set to 1e-2 * sqrt(accuracy). - Setting it to 0.0 is not recommended. Defaults to -1. - tol: Tolerance for termination. - eps: Step size used for numerical approximation of the Jacobian. - options: A dictionary of solver options. - max_evals_grouped: Max number of default gradient evaluations performed simultaneously. - kwargs: additional kwargs for scipy.optimize.minimize. - """ - if options is None: - options = {} - for k, v in list(locals().items()): - if k in self._OPTIONS: - options[k] = v - super().__init__( - "TNC", - options=options, - tol=tol, - max_evals_grouped=max_evals_grouped, - **kwargs, - ) diff --git a/qiskit/algorithms/optimizers/umda.py b/qiskit/algorithms/optimizers/umda.py deleted file mode 100644 index edea27939ade..000000000000 --- a/qiskit/algorithms/optimizers/umda.py +++ /dev/null @@ -1,355 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Univariate Marginal Distribution Algorithm (Estimation-of-Distribution-Algorithm).""" - -from __future__ import annotations - -import warnings - -from collections.abc import Callable -from typing import Any -import numpy as np -from scipy.stats import norm -from qiskit.utils import algorithm_globals - -from .optimizer import OptimizerResult, POINT -from .scipy_optimizer import Optimizer, OptimizerSupportLevel - - -class UMDA(Optimizer): - """Continuous Univariate Marginal Distribution Algorithm (UMDA). - - UMDA [1] is a specific type of Estimation of Distribution Algorithm (EDA) where new individuals - are sampled from univariate normal distributions and are updated in each iteration of the - algorithm by the best individuals found in the previous iteration. - - .. seealso:: - - This original implementation of the UDMA optimizer for Qiskit was inspired by my - (Vicente P. Soloviev) work on the EDAspy Python package [2]. - - EDAs are stochastic search algorithms and belong to the family of the evolutionary algorithms. - The main difference is that EDAs have a probabilistic model which is updated in each iteration - from the best individuals of previous generations (elite selection). Depending on the complexity - of the probabilistic model, EDAs can be classified in different ways. In this case, UMDA is a - univariate EDA as the embedded probabilistic model is univariate. - - UMDA has been compared to some of the already implemented algorithms in Qiskit library to - optimize the parameters of variational algorithms such as QAOA or VQE and competitive results - have been obtained [1]. UMDA seems to provide very good solutions for those circuits in which - the number of layers is not big. - - The optimization process can be personalized depending on the parameters chosen in the - initialization. The main parameter is the population size. The bigger it is, the final result - will be better. However, this increases the complexity of the algorithm and the runtime will - be much heavier. In the work [1] different experiments have been performed where population - size has been set to 20 - 30. - - .. note:: - - The UMDA implementation has more parameters but these have default values for the - initialization for better understanding of the user. For example, ``\alpha`` parameter has - been set to 0.5 and is the percentage of the population which is selected in each iteration - to update the probabilistic model. - - - Example: - - This short example runs UMDA to optimize the parameters of a variational algorithm. Here we - will use the same operator as used in the algorithms introduction, which was originally - computed by Qiskit Nature for an H2 molecule. The minimum energy of the H2 Hamiltonian can - be found quite easily so we are able to set maxiters to a small value. - - .. code-block:: python - - from qiskit.opflow import X, Z, I - from qiskit import Aer - from qiskit.algorithms.optimizers import UMDA - from qiskit.algorithms import QAOA - from qiskit.utils import QuantumInstance - - - H2_op = (-1.052373245772859 * I ^ I) + \ - (0.39793742484318045 * I ^ Z) + \ - (-0.39793742484318045 * Z ^ I) + \ - (-0.01128010425623538 * Z ^ Z) + \ - (0.18093119978423156 * X ^ X) - - p = 2 # Toy example: 2 layers with 2 parameters in each layer: 4 variables - - opt = UMDA(maxiter=100, size_gen=20) - - backend = Aer.get_backend('statevector_simulator') - vqe = QAOA(opt, - quantum_instance=QuantumInstance(backend=backend), - reps=p) - - result = vqe.compute_minimum_eigenvalue(operator=H2_op) - - If it is desired to modify the percentage of individuals considered to update the - probabilistic model, then this code can be used. Here for example we set the 60% instead - of the 50% predefined. - - .. code-block:: python - - opt = UMDA(maxiter=100, size_gen=20, alpha = 0.6) - - backend = Aer.get_backend('statevector_simulator') - vqe = QAOA(opt, - quantum_instance=QuantumInstance(backend=backend), - reps=p) - - result = vqe.compute_minimum_eigenvalue(operator=qubit_op) - - - References: - - [1]: Vicente P. Soloviev, Pedro Larrañaga and Concha Bielza (2022, July). Quantum Parametric - Circuit Optimization with Estimation of Distribution Algorithms. In 2022 The Genetic and - Evolutionary Computation Conference (GECCO). DOI: https://doi.org/10.1145/3520304.3533963 - - [2]: Vicente P. Soloviev. Python package EDAspy. - https://github.com/VicentePerezSoloviev/EDAspy. - """ - - ELITE_FACTOR = 0.4 - STD_BOUND = 0.3 - - def __init__( - self, - maxiter: int = 100, - size_gen: int = 20, - alpha: float = 0.5, - callback: Callable[[int, np.array, float], None] | None = None, - ) -> None: - r""" - Args: - maxiter: Maximum number of iterations. - size_gen: Population size of each generation. - alpha: Percentage (0, 1] of the population to be selected as elite selection. - callback: A callback function passed information in each iteration step. The - information is, in this order: the number of function evaluations, the parameters, - the best function value in this iteration. - """ - - self.size_gen = size_gen - self.maxiter = maxiter - self.alpha = alpha - self._vector: np.ndarray | None = None - # initialization of generation - self._generation: np.ndarray | None = None - self._dead_iter = int(self._maxiter / 5) - - self._truncation_length = int(size_gen * alpha) - - super().__init__() - - self._best_cost_global: float | None = None - self._best_ind_global: int | None = None - self._evaluations: np.ndarray | None = None - - self._n_variables: int | None = None - - self.callback = callback - - def _initialization(self) -> np.ndarray: - vector = np.zeros((4, self._n_variables)) - - vector[0, :] = np.pi # mu - vector[1, :] = 0.5 # std - - return vector - - # build a generation of size SIZE_GEN from prob vector - def _new_generation(self): - """Build a new generation sampled from the vector of probabilities. - Updates the generation pandas dataframe - """ - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - gen = algorithm_globals.random.normal( - self._vector[0, :], self._vector[1, :], [self._size_gen, self._n_variables] - ) - - self._generation = self._generation[: int(self.ELITE_FACTOR * len(self._generation))] - self._generation = np.vstack((self._generation, gen)) - - # truncate the generation at alpha percent - def _truncation(self): - """Selection of the best individuals of the actual generation. - Updates the generation by selecting the best individuals. - """ - best_indices = self._evaluations.argsort()[: self._truncation_length] - self._generation = self._generation[best_indices, :] - self._evaluations = np.take(self._evaluations, best_indices) - - # check each individual of the generation - def _check_generation(self, objective_function): - """Check the cost of each individual in the cost function implemented by the user.""" - self._evaluations = np.apply_along_axis(objective_function, 1, self._generation) - - # update the probability vector - def _update_vector(self): - """From the best individuals update the vector of normal distributions in order to the next - generation can sample from it. Update the vector of normal distributions - """ - - for i in range(self._n_variables): - self._vector[0, i], self._vector[1, i] = norm.fit(self._generation[:, i]) - if self._vector[1, i] < self.STD_BOUND: - self._vector[1, i] = self.STD_BOUND - - def minimize( - self, - fun: Callable[[POINT], float], - x0: POINT, - jac: Callable[[POINT], POINT] | None = None, - bounds: list[tuple[float, float]] | None = None, - ) -> OptimizerResult: - - not_better_count = 0 - result = OptimizerResult() - - if isinstance(x0, float): - x0 = [x0] - self._n_variables = len(x0) - - self._best_cost_global = 999999999999 - self._best_ind_global = 9999999 - history = [] - self._evaluations = np.array(0) - - self._vector = self._initialization() - - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - # initialization of generation - self._generation = algorithm_globals.random.normal( - self._vector[0, :], self._vector[1, :], [self._size_gen, self._n_variables] - ) - - for _ in range(self._maxiter): - self._check_generation(fun) - self._truncation() - self._update_vector() - - best_mae_local: float = min(self._evaluations) - - history.append(best_mae_local) - best_ind_local = np.where(self._evaluations == best_mae_local)[0][0] - best_ind_local = self._generation[best_ind_local] - - # update the best values ever - if best_mae_local < self._best_cost_global: - self._best_cost_global = best_mae_local - self._best_ind_global = best_ind_local - not_better_count = 0 - - else: - not_better_count += 1 - if not_better_count >= self._dead_iter: - break - - if self.callback is not None: - self.callback( - len(history) * self._size_gen, self._best_ind_global, self._best_cost_global - ) - - self._new_generation() - - result.x = self._best_ind_global - result.fun = self._best_cost_global - result.nfev = len(history) * self._size_gen - - return result - - @property - def size_gen(self) -> int: - """Returns the size of the generations (number of individuals per generation)""" - return self._size_gen - - @size_gen.setter - def size_gen(self, value: int): - """ - Sets the size of the generations of the algorithm. - - Args: - value: Size of the generations (number of individuals per generation). - - Raises: - ValueError: If `value` is lower than 1. - """ - if value <= 0: - raise ValueError("The size of the generation should be greater than 0.") - self._size_gen = value - - @property - def maxiter(self) -> int: - """Returns the maximum number of iterations""" - return self._maxiter - - @maxiter.setter - def maxiter(self, value: int): - """ - Sets the maximum number of iterations of the algorithm. - - Args: - value: Maximum number of iterations of the algorithm. - - Raises: - ValueError: If `value` is lower than 1. - """ - if value <= 0: - raise ValueError("The maximum number of iterations should be greater than 0.") - - self._maxiter = value - - @property - def alpha(self) -> float: - """Returns the alpha parameter value (percentage of population selected to update - probabilistic model)""" - return self._alpha - - @alpha.setter - def alpha(self, value: float): - """ - Sets the alpha parameter (percentage of individuals selected to update the probabilistic - model) - - Args: - value: Percentage (0,1] of generation selected to update the probabilistic model. - - Raises: - ValueError: If `value` is lower than 0 or greater than 1. - """ - if (value <= 0) or (value > 1): - raise ValueError(f"alpha must be in the range (0, 1], value given was {value}") - - self._alpha = value - - @property - def settings(self) -> dict[str, Any]: - return { - "maxiter": self.maxiter, - "alpha": self.alpha, - "size_gen": self.size_gen, - "callback": self.callback, - } - - def get_support_level(self): - """Get the support level dictionary.""" - return { - "gradient": OptimizerSupportLevel.ignored, - "bounds": OptimizerSupportLevel.ignored, - "initial_point": OptimizerSupportLevel.required, - } diff --git a/qiskit/algorithms/phase_estimators/__init__.py b/qiskit/algorithms/phase_estimators/__init__.py deleted file mode 100644 index 2ef5b089aaed..000000000000 --- a/qiskit/algorithms/phase_estimators/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Phase Estimators.""" - -from .phase_estimator import PhaseEstimator -from .phase_estimation import PhaseEstimation -from .phase_estimation_result import PhaseEstimationResult -from .phase_estimation_scale import PhaseEstimationScale -from .hamiltonian_phase_estimation import HamiltonianPhaseEstimation -from .hamiltonian_phase_estimation_result import HamiltonianPhaseEstimationResult -from .ipe import IterativePhaseEstimation - -__all__ = [ - "PhaseEstimator", - "PhaseEstimation", - "PhaseEstimationResult", - "PhaseEstimationScale", - "HamiltonianPhaseEstimation", - "HamiltonianPhaseEstimationResult", - "IterativePhaseEstimation", -] diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py deleted file mode 100644 index 87907c39e304..000000000000 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation.py +++ /dev/null @@ -1,309 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Phase estimation for the spectrum of a Hamiltonian""" - -from __future__ import annotations - -import warnings - -from qiskit import QuantumCircuit -from qiskit.utils import QuantumInstance -from qiskit.utils.deprecation import deprecate_arg -from qiskit.opflow import ( - SummedOp, - PauliOp, - MatrixOp, - PauliSumOp, - StateFn, - EvolutionBase, - PauliTrotterEvolution, - I, -) -from qiskit.providers import Backend -from .phase_estimation import PhaseEstimation -from .hamiltonian_phase_estimation_result import HamiltonianPhaseEstimationResult -from .phase_estimation_scale import PhaseEstimationScale -from ...circuit.library import PauliEvolutionGate -from ...primitives import BaseSampler -from ...quantum_info import SparsePauliOp, Statevector, Pauli -from ...synthesis import EvolutionSynthesis - - -class HamiltonianPhaseEstimation: - r"""Run the Quantum Phase Estimation algorithm to find the eigenvalues of a Hermitian operator. - - This class is nearly the same as :class:`~qiskit.algorithms.PhaseEstimation`, differing only - in that the input in that class is a unitary operator, whereas here the input is a Hermitian - operator from which a unitary will be obtained by scaling and exponentiating. The scaling is - performed in order to prevent the phases from wrapping around :math:`2\pi`. - The problem of estimating eigenvalues :math:`\lambda_j` of the Hermitian operator - :math:`H` is solved by running a circuit representing - - .. math:: - - \exp(i b H) |\psi\rangle = \sum_j \exp(i b \lambda_j) c_j |\lambda_j\rangle, - - where the input state is - - .. math:: - - |\psi\rangle = \sum_j c_j |\lambda_j\rangle, - - and :math:`\lambda_j` are the eigenvalues of :math:`H`. - - Here, :math:`b` is a scaling factor sufficiently large to map positive :math:`\lambda` to - :math:`[0,\pi)` and negative :math:`\lambda` to :math:`[\pi,2\pi)`. Each time the circuit is - run, one measures a phase corresponding to :math:`lambda_j` with probability :math:`|c_j|^2`. - - If :math:`H` is a Pauli sum, the bound :math:`b` is computed from the sum of the absolute - values of the coefficients of the terms. There is no way to reliably recover eigenvalues - from phases very near the endpoints of these intervals. Because of this you should be aware - that for degenerate cases, such as :math:`H=Z`, the eigenvalues :math:`\pm 1` will be - mapped to the same phase, :math:`\pi`, and so cannot be distinguished. In this case, you need - to specify a larger bound as an argument to the method ``estimate``. - - This class uses and works together with :class:`~qiskit.algorithms.PhaseEstimationScale` to - manage scaling the Hamiltonian and the phases that are obtained by the QPE algorithm. This - includes setting, or computing, a bound on the eigenvalues of the operator, using this - bound to obtain a scale factor, scaling the operator, and shifting and scaling the measured - phases to recover the eigenvalues. - - Note that, although we speak of "evolving" the state according the Hamiltonian, in the - present algorithm, we are not actually considering time evolution. Rather, the role of time is - played by the scaling factor, which is chosen to best extract the eigenvalues of the - Hamiltonian. - - A few of the ideas in the algorithm may be found in Ref. [1]. - - **Reference:** - - [1]: Quantum phase estimation of multiple eigenvalues for small-scale (noisy) experiments - T.E. O'Brien, B. Tarasinski, B.M. Terhal - `arXiv:1809.09697 `_ - - """ - - @deprecate_arg( - "quantum_instance", - additional_msg=( - "Instead, use the ``sampler`` argument. See https://qisk.it/algo_migration for a " - "migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - num_evaluation_qubits: int, - quantum_instance: QuantumInstance | Backend | None = None, - sampler: BaseSampler | None = None, - ) -> None: - r""" - Args: - num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will - be estimated as a binary string with this many bits. - quantum_instance: Deprecated: The quantum instance on which - the circuit will be run. - sampler: The sampler primitive on which the circuit will be sampled. - """ - # Avoid double warning on deprecated used of `quantum_instance`. - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - self._phase_estimation = PhaseEstimation( - num_evaluation_qubits=num_evaluation_qubits, - quantum_instance=quantum_instance, - sampler=sampler, - ) - - def _get_scale(self, hamiltonian, bound=None) -> PhaseEstimationScale: - if bound is None: - return PhaseEstimationScale.from_pauli_sum(hamiltonian) - - return PhaseEstimationScale(bound) - - def _get_unitary( - self, hamiltonian, pe_scale, evolution: EvolutionSynthesis | EvolutionBase - ) -> QuantumCircuit: - """Evolve the Hamiltonian to obtain a unitary. - - Apply the scaling to the Hamiltonian that has been computed from an eigenvalue bound - and compute the unitary by applying the evolution object. - """ - - if self._phase_estimation._sampler is not None: - - evo = PauliEvolutionGate(hamiltonian, -pe_scale.scale, synthesis=evolution) - unitary = QuantumCircuit(evo.num_qubits) - unitary.append(evo, unitary.qubits) - - return unitary.decompose().decompose() - else: - # scale so that phase does not wrap. - scaled_hamiltonian = -pe_scale.scale * hamiltonian - unitary = evolution.convert(scaled_hamiltonian.exp_i()) - if not isinstance(unitary, QuantumCircuit): - unitary = unitary.to_circuit() - - return unitary.decompose().decompose() - - # Decomposing twice allows some 1Q Hamiltonians to give correct results - # when using MatrixEvolution(), that otherwise would give incorrect results. - # It does not break any others that we tested. - - def estimate( - self, - hamiltonian: PauliOp | MatrixOp | SummedOp | Pauli | SparsePauliOp | PauliSumOp, - state_preparation: StateFn | QuantumCircuit | Statevector | None = None, - evolution: EvolutionSynthesis | EvolutionBase | None = None, - bound: float | None = None, - ) -> HamiltonianPhaseEstimationResult: - """Run the Hamiltonian phase estimation algorithm. - - Args: - hamiltonian: A Hermitian operator. If the algorithm is used with a ``Sampler`` - primitive, the allowed types are ``Pauli``, ``SparsePauliOp``, and ``PauliSumOp``. - If the algorithm is used with a ``QuantumInstance``, ``PauliOp, ``MatrixOp``, - ``PauliSumOp``, and ``SummedOp`` types are allowed. - state_preparation: The ``StateFn`` to be prepared, whose eigenphase will be - measured. If this parameter is omitted, no preparation circuit will be run and - input state will be the all-zero state in the computational basis. - evolution: An evolution converter that generates a unitary from ``hamiltonian``. If - ``None``, then the default ``PauliTrotterEvolution`` is used. - bound: An upper bound on the absolute value of the eigenvalues of - ``hamiltonian``. If omitted, then ``hamiltonian`` must be a Pauli sum, or a - ``PauliOp``, in which case a bound will be computed. If ``hamiltonian`` - is a ``MatrixOp``, then ``bound`` may not be ``None``. The tighter the bound, - the higher the resolution of computed phases. - - Returns: - ``HamiltonianPhaseEstimationResult`` instance containing the result of the estimation - and diagnostic information. - - Raises: - TypeError: If ``evolution`` is not of type ``EvolutionSynthesis`` when a ``Sampler`` is - provided. - TypeError: If ``hamiltonian`` type is not ``Pauli`` or ``SparsePauliOp`` or - ``PauliSumOp`` when a ``Sampler`` is provided. - ValueError: If ``bound`` is ``None`` and ``hamiltonian`` is not a Pauli sum, i.e. a - ``PauliSumOp`` or a ``SummedOp`` whose terms are of type ``PauliOp``. - TypeError: If ``evolution`` is not of type ``EvolutionBase`` when no ``Sampler`` is - provided. - """ - if self._phase_estimation._sampler is not None: - if evolution is not None and not isinstance(evolution, EvolutionSynthesis): - raise TypeError(f"Expecting type EvolutionSynthesis, got {type(evolution)}") - if not isinstance(hamiltonian, (Pauli, SparsePauliOp, PauliSumOp)): - raise TypeError( - f"Expecting Hamiltonian type Pauli, SparsePauliOp or PauliSumOp, " - f"got {type(hamiltonian)}." - ) - - if isinstance(state_preparation, Statevector): - circuit = QuantumCircuit(state_preparation.num_qubits) - circuit.prepare_state(state_preparation.data) - state_preparation = circuit - if isinstance(hamiltonian, PauliSumOp): - id_coefficient, hamiltonian_no_id = _remove_identity_pauli_sum_op(hamiltonian) - else: - id_coefficient = 0.0 - hamiltonian_no_id = hamiltonian - pe_scale = self._get_scale(hamiltonian_no_id, bound) - unitary = self._get_unitary(hamiltonian_no_id, pe_scale, evolution) - else: - if evolution is None: - evolution = PauliTrotterEvolution() - elif not isinstance(evolution, EvolutionBase): - raise TypeError(f"Expecting type EvolutionBase, got {type(evolution)}") - - if isinstance(hamiltonian, PauliSumOp): - hamiltonian = hamiltonian.to_pauli_op() - elif isinstance(hamiltonian, PauliOp): - hamiltonian = SummedOp([hamiltonian]) - - if isinstance(hamiltonian, SummedOp): - # remove identitiy terms - # The term prop to the identity is removed from hamiltonian. - # This is done for three reasons: - # 1. Work around an unknown bug that otherwise causes the energies to be wrong in some - # cases. - # 2. Allow working with a simpler Hamiltonian, one with fewer terms. - # 3. Tighten the bound on the eigenvalues so that the spectrum is better resolved, i.e. - # occupies more of the range of values representable by the qubit register. - # The coefficient of this term will be added to the eigenvalues. - id_coefficient, hamiltonian_no_id = _remove_identity(hamiltonian) - # get the rescaling object - pe_scale = self._get_scale(hamiltonian_no_id, bound) - - # get the unitary - unitary = self._get_unitary(hamiltonian_no_id, pe_scale, evolution) - - elif isinstance(hamiltonian, MatrixOp): - if bound is None: - raise ValueError("bound must be specified if Hermitian operator is MatrixOp") - - # Do not subtract an identity term from the matrix, so do not compensate. - id_coefficient = 0.0 - pe_scale = self._get_scale(hamiltonian, bound) - unitary = self._get_unitary(hamiltonian, pe_scale, evolution) - else: - raise TypeError(f"Hermitian operator of type {type(hamiltonian)} not supported.") - - if state_preparation is not None and isinstance(state_preparation, StateFn): - state_preparation = state_preparation.to_circuit_op().to_circuit() - # run phase estimation - phase_estimation_result = self._phase_estimation.estimate( - unitary=unitary, state_preparation=state_preparation - ) - return HamiltonianPhaseEstimationResult( - phase_estimation_result=phase_estimation_result, - id_coefficient=id_coefficient, - phase_estimation_scale=pe_scale, - ) - - -def _remove_identity(pauli_sum: SummedOp): - """Remove any identity operators from `pauli_sum`. Return - the sum of the coefficients of the identities and the new operator. - """ - idcoeff = 0.0 - ops = [] - for op in pauli_sum: - p = op.primitive - if p.x.any() or p.z.any(): - ops.append(op) - else: - idcoeff += op.coeff - - return idcoeff, SummedOp(ops) - - -def _remove_identity_pauli_sum_op(pauli_sum: PauliSumOp | SparsePauliOp): - """Remove any identity operators from ``pauli_sum``. Return - the sum of the coefficients of the identities and the new operator. - """ - - def _get_identity(size): - identity = I - for _ in range(size - 1): - identity = identity ^ I - return identity - - idcoeff = 0.0 - if isinstance(pauli_sum, PauliSumOp): - for operator in pauli_sum: - if operator.primitive.paulis == ["I" * pauli_sum.num_qubits]: - idcoeff += operator.primitive.coeffs[0] - pauli_sum = pauli_sum - operator.primitive.coeffs[0] * _get_identity( - pauli_sum.num_qubits - ) - - return idcoeff, pauli_sum.reduce() diff --git a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py b/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py deleted file mode 100644 index ce844427b04a..000000000000 --- a/qiskit/algorithms/phase_estimators/hamiltonian_phase_estimation_result.py +++ /dev/null @@ -1,108 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Result from running HamiltonianPhaseEstimation""" - -from __future__ import annotations - -from collections.abc import Mapping -from typing import cast -from qiskit.algorithms.algorithm_result import AlgorithmResult -from .phase_estimation_result import PhaseEstimationResult -from .phase_estimation_scale import PhaseEstimationScale - - -class HamiltonianPhaseEstimationResult(AlgorithmResult): - """Store and manipulate results from running `HamiltonianPhaseEstimation`. - - This API of this class is nearly the same as `PhaseEstimatorResult`, differing only in - the presence of an additional keyword argument in the methods. If `scaled` - is `False`, then the phases are not translated and scaled to recover the - eigenvalues of the Hamiltonian. Instead `phi` in :math:`[0, 1)` is returned, - as is the case when then unitary is not derived from a Hamiltonian. - - This class is meant to be instantiated via `HamiltonianPhaseEstimation.estimate`. - """ - - def __init__( - self, - phase_estimation_result: PhaseEstimationResult, - phase_estimation_scale: PhaseEstimationScale, - id_coefficient: float, - ) -> None: - """ - Args: - phase_estimation_result: The result object returned by PhaseEstimation.estimate. - phase_estimation_scale: object used to scale phases to obtain eigenvalues. - id_coefficient: The coefficient of the identity term in the Hamiltonian. - Eigenvalues are computed without this term so that the - coefficient must added to give correct eigenvalues. - This is done automatically when retrieving eigenvalues. - """ - super().__init__() - self._phase_estimation_scale = phase_estimation_scale - self._id_coefficient = id_coefficient - self._phase_estimation_result = phase_estimation_result - - def filter_phases( - self, cutoff: float = 0.0, scaled: bool = True, as_float: bool = True - ) -> Mapping[str | float, float]: - """Filter phases as does `PhaseEstimatorResult.filter_phases`, with - the addition that `phi` is shifted and translated to return eigenvalues - of the Hamiltonian. - - Args: - cutoff: Minimum weight of number of counts required to keep a bit string. - The default value is `0.0`. - scaled: If False, return `phi` in :math:`[0, 1)` rather than the eigenvalues of - the Hamiltonian. - as_float: If `True`, returned keys are floats in :math:`[0.0, 1.0)`. If `False` - returned keys are bit strings. - - Raises: - ValueError: if `as_float` is `False` and `scaled` is `True`. - - Returns: - A dict of filtered phases. - """ - if scaled and not as_float: - raise ValueError("`as_float` must be `True` if `scaled` is `True`.") - - phases = self._phase_estimation_result.filter_phases(cutoff, as_float=as_float) - if scaled: - return cast( - dict, self._phase_estimation_scale.scale_phases(phases, self._id_coefficient) - ) - else: - return cast(dict, phases) - - @property - def phase(self) -> float: - """The most likely phase of the unitary corresponding to the Hamiltonian. - - Returns: - The most likely phase. - """ - return self._phase_estimation_result.phase - - @property - def most_likely_eigenvalue(self) -> float: - """The most likely eigenvalue of the Hamiltonian. - - This method calls `most_likely_phase` and scales the result to - obtain an eigenvalue. - - Returns: - The most likely eigenvalue of the Hamiltonian. - """ - phase = self.phase - return self._phase_estimation_scale.scale_phase(phase, self._id_coefficient) diff --git a/qiskit/algorithms/phase_estimators/ipe.py b/qiskit/algorithms/phase_estimators/ipe.py deleted file mode 100644 index e8e583027a92..000000000000 --- a/qiskit/algorithms/phase_estimators/ipe.py +++ /dev/null @@ -1,229 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - - -"""The Iterative Quantum Phase Estimation Algorithm.""" - -from __future__ import annotations - -import numpy - -import qiskit -from qiskit.circuit import QuantumCircuit, QuantumRegister -from qiskit.circuit.classicalregister import ClassicalRegister -from qiskit.providers import Backend -from qiskit.utils import QuantumInstance -from qiskit.utils.deprecation import deprecate_arg -from qiskit.algorithms.exceptions import AlgorithmError -from .phase_estimator import PhaseEstimator -from .phase_estimator import PhaseEstimatorResult -from ...primitives import BaseSampler - - -class IterativePhaseEstimation(PhaseEstimator): - """Run the Iterative quantum phase estimation (QPE) algorithm. - - Given a unitary circuit and a circuit preparing an eigenstate, return the phase of the - eigenvalue as a number in :math:`[0,1)` using the iterative phase estimation algorithm. - - [1]: Dobsicek et al. (2006), Arbitrary accuracy iterative phase estimation algorithm as a two - qubit benchmark, `arxiv/quant-ph/0610214 `_ - """ - - @deprecate_arg( - "quantum_instance", - additional_msg=( - "Instead, use the ``sampler`` argument. See https://qisk.it/algo_migration for a " - "migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - num_iterations: int, - quantum_instance: QuantumInstance | Backend | None = None, - sampler: BaseSampler | None = None, - ) -> None: - r""" - Args: - num_iterations: The number of iterations (rounds) of the phase estimation to run. - quantum_instance: Deprecated: The quantum instance on which the - circuit will be run. - sampler: The sampler primitive on which the circuit will be sampled. - - Raises: - ValueError: if num_iterations is not greater than zero. - AlgorithmError: If neither sampler nor quantum instance is provided. - """ - if sampler is None and quantum_instance is None: - raise AlgorithmError( - "Neither a sampler nor a quantum instance was provided. Please provide one of them." - ) - if isinstance(quantum_instance, Backend): - quantum_instance = QuantumInstance(quantum_instance) - self._quantum_instance = quantum_instance - if num_iterations <= 0: - raise ValueError("`num_iterations` must be greater than zero.") - self._num_iterations = num_iterations - self._sampler = sampler - - def construct_circuit( - self, - unitary: QuantumCircuit, - state_preparation: QuantumCircuit, - k: int, - omega: float = 0.0, - measurement: bool = False, - ) -> QuantumCircuit: - """Construct the kth iteration Quantum Phase Estimation circuit. - - For details of parameters, see Fig. 2 in https://arxiv.org/pdf/quant-ph/0610214.pdf. - - Args: - unitary: The circuit representing the unitary operator whose eigenvalue (via phase) - will be measured. - state_preparation: The circuit that prepares the state whose eigenphase will be - measured. If this parameter is omitted, no preparation circuit - will be run and input state will be the all-zero state in the - computational basis. - k: the iteration idx. - omega: the feedback angle. - measurement: Boolean flag to indicate if measurement should - be included in the circuit. - - Returns: - QuantumCircuit: the quantum circuit per iteration - """ - k = self._num_iterations if k is None else k - # The auxiliary (phase measurement) qubit - phase_register = QuantumRegister(1, name="a") - eigenstate_register = QuantumRegister(unitary.num_qubits, name="q") - qc = QuantumCircuit(eigenstate_register) - qc.add_register(phase_register) - if isinstance(state_preparation, QuantumCircuit): - qc.append(state_preparation, eigenstate_register) - elif state_preparation is not None: - qc += state_preparation.construct_circuit("circuit", eigenstate_register) - # hadamard on phase_register[0] - qc.h(phase_register[0]) - # controlled-U - # TODO: We may want to allow flexibility in how the power is computed - # For example, it may be desirable to compute the power via Trotterization, if - # we are doing Trotterization anyway. - unitary_power = unitary.power(2 ** (k - 1)).control() - qc = qc.compose(unitary_power, [unitary.num_qubits] + list(range(0, unitary.num_qubits))) - qc.p(omega, phase_register[0]) - # hadamard on phase_register[0] - qc.h(phase_register[0]) - if measurement: - c = ClassicalRegister(1, name="c") - qc.add_register(c) - qc.measure(phase_register, c) - return qc - - def _estimate_phase_iteratively(self, unitary, state_preparation): - """ - Main loop of iterative phase estimation. - """ - omega_coef = 0 - # k runs from the number of iterations back to 1 - for k in range(self._num_iterations, 0, -1): - omega_coef /= 2 - - if self._sampler is not None: - - qc = self.construct_circuit( - unitary, state_preparation, k, -2 * numpy.pi * omega_coef, True - ) - try: - sampler_job = self._sampler.run([qc]) - result = sampler_job.result().quasi_dists[0] - except Exception as exc: - raise AlgorithmError("The primitive job failed!") from exc - x = 1 if result.get(1, 0) > result.get(0, 0) else 0 - - elif self._quantum_instance.is_statevector: - qc = self.construct_circuit( - unitary, state_preparation, k, -2 * numpy.pi * omega_coef, measurement=False - ) - result = self._quantum_instance.execute(qc) - complete_state_vec = result.get_statevector(qc) - ancilla_density_mat = qiskit.quantum_info.partial_trace( - complete_state_vec, range(unitary.num_qubits) - ) - ancilla_density_mat_diag = numpy.diag(ancilla_density_mat) - max_amplitude = max( - ancilla_density_mat_diag.min(), ancilla_density_mat_diag.max(), key=abs - ) - x = numpy.where(ancilla_density_mat_diag == max_amplitude)[0][0] - else: - qc = self.construct_circuit( - unitary, state_preparation, k, -2 * numpy.pi * omega_coef, measurement=True - ) - measurements = self._quantum_instance.execute(qc).get_counts(qc) - x = 1 if measurements.get("1", 0) > measurements.get("0", 0) else 0 - omega_coef = omega_coef + x / 2 - return omega_coef - - # pylint: disable=signature-differs - def estimate( - self, unitary: QuantumCircuit, state_preparation: QuantumCircuit - ) -> "IterativePhaseEstimationResult": - """ - Estimate the eigenphase of the input unitary and initial-state pair. - - Args: - unitary: The circuit representing the unitary operator whose eigenvalue (via phase) - will be measured. - state_preparation: The circuit that prepares the state whose eigenphase will be - measured. If this parameter is omitted, no preparation circuit - will be run and input state will be the all-zero state in the - computational basis. - - Returns: - Estimated phase in an IterativePhaseEstimationResult object. - - Raises: - AlgorithmError: If neither sampler nor quantum instance is provided. - """ - phase = self._estimate_phase_iteratively(unitary, state_preparation) - - return IterativePhaseEstimationResult(self._num_iterations, phase) - - -class IterativePhaseEstimationResult(PhaseEstimatorResult): - """Phase Estimation Result.""" - - def __init__(self, num_iterations: int, phase: float) -> None: - """ - Args: - num_iterations: number of iterations used in the phase estimation. - phase: the estimated phase. - """ - - self._num_iterations = num_iterations - self._phase = phase - - @property - def phase(self) -> float: - r"""Return the estimated phase as a number in :math:`[0.0, 1.0)`. - - 1.0 corresponds to a phase of :math:`2\pi`. It is assumed that the input vector is an - eigenvector of the unitary so that the peak of the probability density occurs at the bit - string that most closely approximates the true phase. - """ - return self._phase - - @property - def num_iterations(self) -> int: - r"""Return the number of iterations used in the estimation algorithm.""" - return self._num_iterations diff --git a/qiskit/algorithms/phase_estimators/phase_estimation.py b/qiskit/algorithms/phase_estimators/phase_estimation.py deleted file mode 100644 index a3bc1d59c60d..000000000000 --- a/qiskit/algorithms/phase_estimators/phase_estimation.py +++ /dev/null @@ -1,268 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - - -"""The Quantum Phase Estimation Algorithm.""" - -from __future__ import annotations - -import numpy - -from qiskit.circuit import QuantumCircuit -import qiskit -from qiskit import circuit -from qiskit.circuit.classicalregister import ClassicalRegister -from qiskit.providers import Backend -from qiskit.utils import QuantumInstance -from qiskit.utils.deprecation import deprecate_arg -from qiskit.result import Result -from qiskit.algorithms.exceptions import AlgorithmError -from .phase_estimation_result import PhaseEstimationResult, _sort_phases -from .phase_estimator import PhaseEstimator -from ...primitives import BaseSampler - - -class PhaseEstimation(PhaseEstimator): - r"""Run the Quantum Phase Estimation (QPE) algorithm. - - This runs QPE with a multi-qubit register for reading the phases [1] - of input states. - - The algorithm takes as input a unitary :math:`U` and a state :math:`|\psi\rangle`, - which may be written - - .. math:: - - |\psi\rangle = \sum_j c_j |\phi_j\rangle, - - where :math:`|\phi_j\rangle` are eigenstates of :math:`U`. We prepare the quantum register - in the state :math:`|\psi\rangle` then apply :math:`U` leaving the register in the state - - .. math:: - - U|\psi\rangle = \sum_j \exp(i \phi_j) c_j |\phi_j\rangle. - - In the ideal case, one then measures the phase :math:`\phi_j` with probability - :math:`|c_j|^2`. In practice, many (or all) of the bit strings may be measured due to - noise and the possibility that :math:`\phi_j` may not be representable exactly by the - output register. In the latter case the probability for each eigenphase will be spread - across bitstrings, with amplitudes that decrease with distance from the bitstring most - closely approximating the eigenphase. - - The main input to the constructor is the number of qubits in the phase-reading register. - For phase estimation, there are two methods: - - first. `estimate`, which takes a state preparation circuit to prepare an input state, and - a unitary that will act on the input state. In this case, an instance of - :class:`qiskit.circuit.PhaseEstimation`, a QPE circuit, containing - the state preparation and input unitary will be constructed. - second. `estimate_from_pe_circuit`, which takes a quantum-phase-estimation circuit in which - the unitary and state preparation are already embedded. - - In both estimation methods, the QPE circuit is run on a backend - and the frequencies or counts of the phases represented by bitstrings - are recorded. The results are returned as an instance of - :class:`~qiskit.algorithms.phase_estimator_result.PhaseEstimationResult`. - - **Reference:** - - [1]: Michael A. Nielsen and Isaac L. Chuang. 2011. - Quantum Computation and Quantum Information: 10th Anniversary Edition (10th ed.). - Cambridge University Press, New York, NY, USA. - - """ - - @deprecate_arg( - "quantum_instance", - additional_msg=( - "Instead, use the ``sampler`` argument. See https://qisk.it/algo_migration for a " - "migration guide." - ), - since="0.24.0", - ) - def __init__( - self, - num_evaluation_qubits: int, - quantum_instance: QuantumInstance | Backend | None = None, - sampler: BaseSampler | None = None, - ) -> None: - r""" - Args: - num_evaluation_qubits: The number of qubits used in estimating the phase. The phase will - be estimated as a binary string with this many bits. - quantum_instance: Deprecated: The quantum instance on which the - circuit will be run. - sampler: The sampler primitive on which the circuit will be sampled. - - Raises: - AlgorithmError: If neither sampler nor quantum instance is provided. - """ - if sampler is None and quantum_instance is None: - raise AlgorithmError( - "Neither a sampler nor a quantum instance was provided. Please provide one of them." - ) - self._measurements_added = False - if num_evaluation_qubits is not None: - self._num_evaluation_qubits = num_evaluation_qubits - - if isinstance(quantum_instance, Backend): - quantum_instance = QuantumInstance(quantum_instance) - self._quantum_instance = quantum_instance - self._sampler = sampler - - def construct_circuit( - self, unitary: QuantumCircuit, state_preparation: QuantumCircuit | None = None - ) -> QuantumCircuit: - """Return the circuit to be executed to estimate phases. - - This circuit includes as sub-circuits the core phase estimation circuit, - with the addition of the state-preparation circuit and possibly measurement instructions. - """ - num_evaluation_qubits = self._num_evaluation_qubits - num_unitary_qubits = unitary.num_qubits - - pe_circuit = circuit.library.PhaseEstimation(num_evaluation_qubits, unitary) - - if state_preparation is not None: - pe_circuit.compose( - state_preparation, - qubits=range(num_evaluation_qubits, num_evaluation_qubits + num_unitary_qubits), - inplace=True, - front=True, - ) - - return pe_circuit - - def _add_measurement_if_required(self, pe_circuit): - if self._sampler is not None or not self._quantum_instance.is_statevector: - # Measure only the evaluation qubits. - regname = "meas" - creg = ClassicalRegister(self._num_evaluation_qubits, regname) - pe_circuit.add_register(creg) - pe_circuit.barrier() - pe_circuit.measure( - range(self._num_evaluation_qubits), range(self._num_evaluation_qubits) - ) - - return circuit - - def _compute_phases( - self, num_unitary_qubits: int, circuit_result: Result - ) -> numpy.ndarray | qiskit.result.Counts: - """Compute frequencies/counts of phases from the result of running the QPE circuit. - - How the frequencies are computed depends on whether the backend computes amplitude or - samples outcomes. - - 1) If the backend is a statevector simulator, then the reduced density matrix of the - phase-reading register is computed from the combined phase-reading- and input-state - registers. The elements of the diagonal :math:`(i, i)` give the probability to measure the - each of the states `i`. The index `i` expressed as a binary integer with the LSB rightmost - gives the state of the phase-reading register with the LSB leftmost when interpreted as a - phase. In order to maintain the compact representation, the phases are maintained as decimal - integers. They may be converted to other forms via the results object, - `PhaseEstimationResult` or `HamiltonianPhaseEstimationResult`. - - 2) If the backend samples bitstrings, then the counts are first retrieved as a dict. The - binary strings (the keys) are then reversed so that the LSB is rightmost and the counts are - converted to frequencies. Then the keys are sorted according to increasing phase, so that - they can be easily understood when displaying or plotting a histogram. - - Args: - num_unitary_qubits: The number of qubits in the unitary. - circuit_result: the result object returned by the backend that ran the QPE circuit. - - Returns: - Either a dict or numpy.ndarray representing the frequencies of the phases. - - """ - if self._quantum_instance.is_statevector: - state_vec = circuit_result.get_statevector() - evaluation_density_matrix = qiskit.quantum_info.partial_trace( - state_vec, - range( - self._num_evaluation_qubits, self._num_evaluation_qubits + num_unitary_qubits - ), - ) - phases = evaluation_density_matrix.probabilities() - else: - # return counts with keys sorted numerically - num_shots = circuit_result.results[0].shots - counts = circuit_result.get_counts() - phases = {k[::-1]: counts[k] / num_shots for k in counts.keys()} - phases = _sort_phases(phases) - phases = qiskit.result.Counts( - phases, memory_slots=counts.memory_slots, creg_sizes=counts.creg_sizes - ) - - return phases - - def estimate_from_pe_circuit( - self, pe_circuit: QuantumCircuit, num_unitary_qubits: int - ) -> PhaseEstimationResult: - """Run the phase estimation algorithm on a phase estimation circuit - - Args: - pe_circuit: The phase estimation circuit. - num_unitary_qubits: Must agree with the number of qubits in the unitary in `pe_circuit`. - - Returns: - An instance of qiskit.algorithms.phase_estimator_result.PhaseEstimationResult. - - Raises: - AlgorithmError: Primitive job failed. - """ - - self._add_measurement_if_required(pe_circuit) - - if self._sampler is not None: - try: - circuit_job = self._sampler.run([pe_circuit]) - circuit_result = circuit_job.result() - except Exception as exc: - raise AlgorithmError("The primitive job failed!") from exc - phases = circuit_result.quasi_dists[0] - phases_bitstrings = {} - for key, phase in phases.items(): - bitstring_key = self._get_reversed_bitstring(self._num_evaluation_qubits, key) - phases_bitstrings[bitstring_key] = phase - phases = phases_bitstrings - - else: - circuit_result = self._quantum_instance.execute(pe_circuit) - phases = self._compute_phases(num_unitary_qubits, circuit_result) - return PhaseEstimationResult( - self._num_evaluation_qubits, circuit_result=circuit_result, phases=phases - ) - - def estimate( - self, - unitary: QuantumCircuit, - state_preparation: QuantumCircuit | None = None, - ) -> PhaseEstimationResult: - """Build a phase estimation circuit and run the corresponding algorithm. - - Args: - unitary: The circuit representing the unitary operator whose eigenvalues (via phase) - will be measured. - state_preparation: The circuit that prepares the state whose eigenphase will be - measured. If this parameter is omitted, no preparation circuit - will be run and input state will be the all-zero state in the - computational basis. - - Returns: - An instance of qiskit.algorithms.phase_estimator_result.PhaseEstimationResult. - """ - pe_circuit = self.construct_circuit(unitary, state_preparation) - num_unitary_qubits = unitary.num_qubits - - return self.estimate_from_pe_circuit(pe_circuit, num_unitary_qubits) diff --git a/qiskit/algorithms/phase_estimators/phase_estimation_result.py b/qiskit/algorithms/phase_estimators/phase_estimation_result.py deleted file mode 100644 index 8d87571af7e7..000000000000 --- a/qiskit/algorithms/phase_estimators/phase_estimation_result.py +++ /dev/null @@ -1,174 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Result of running PhaseEstimation""" -from __future__ import annotations -import numpy - -from qiskit.utils.deprecation import deprecate_func -from qiskit.result import Result -from .phase_estimator import PhaseEstimatorResult - - -class PhaseEstimationResult(PhaseEstimatorResult): - """Store and manipulate results from running `PhaseEstimation`. - - This class is instantiated by the ``PhaseEstimation`` class, not via user code. - The ``PhaseEstimation`` class generates a list of phases and corresponding weights. Upon - completion it returns the results as an instance of this class. The main method for - accessing the results is `filter_phases`. - - The canonical phase satisfying the ``PhaseEstimator`` interface, returned by the - attribute `phase`, is the most likely phase. - """ - - def __init__( - self, - num_evaluation_qubits: int, - circuit_result: Result, - phases: numpy.ndarray | dict[str, float], - ) -> None: - """ - Args: - num_evaluation_qubits: number of qubits in phase-readout register. - circuit_result: result object returned by method running circuit. - phases: ndarray or dict of phases and frequencies determined by QPE. - """ - super().__init__() - - self._phases = phases - # int: number of qubits in phase-readout register - self._num_evaluation_qubits = num_evaluation_qubits - self._circuit_result = circuit_result - - @property - def phases(self) -> numpy.ndarray | dict: - """Return all phases and their frequencies computed by QPE. - - This is an array or dict whose values correspond to weights on bit strings. - """ - return self._phases - - @property - def circuit_result(self) -> Result: - """Return the result object returned by running the QPE circuit (on hardware or simulator). - - This is useful for inspecting and troubleshooting the QPE algorithm. - """ - return self._circuit_result - - @property - @deprecate_func( - additional_msg="Instead, use the property ``phase``, which behaves the same.", - since="0.18.0", - is_property=True, - ) - def most_likely_phase(self) -> float: - r"""DEPRECATED - Return the most likely phase as a number in :math:`[0.0, 1.0)`. - - 1.0 corresponds to a phase of :math:`2\pi`. This selects the phase corresponding - to the bit string with the highesest probability. This is the most likely phase. - """ - return self.phase - - @property - def phase(self) -> float: - r"""Return the most likely phase as a number in :math:`[0.0, 1.0)`. - - 1.0 corresponds to a phase of :math:`2\pi`. This selects the phase corresponding - to the bit string with the highesest probability. This is the most likely phase. - """ - if isinstance(self.phases, dict): - binary_phase_string = max(self.phases, key=self.phases.get) - else: - # numpy.argmax ignores complex part of number. But, we take abs anyway - idx = numpy.argmax(abs(self.phases)) - binary_phase_string = numpy.binary_repr(idx, self._num_evaluation_qubits)[::-1] - phase = _bit_string_to_phase(binary_phase_string) - return phase - - def filter_phases(self, cutoff: float = 0.0, as_float: bool = True) -> dict: - """Return a filtered dict of phases (keys) and frequencies (values). - - Only phases with frequencies (counts) larger than `cutoff` are included. - It is assumed that the `run` method has been called so that the phases have been computed. - When using a noiseless, shot-based simulator to read a single phase that can - be represented exactly by `num_evaluation_qubits`, all the weight will - be concentrated on a single phase. In all other cases, many, or all, bit - strings will have non-zero weight. This method is useful for filtering - out these uninteresting bit strings. - - Args: - cutoff: Minimum weight of number of counts required to keep a bit string. - The default value is `0.0`. - as_float: If `True`, returned keys are floats in :math:`[0.0, 1.0)`. If `False` - returned keys are bit strings. - - Returns: - A filtered dict of phases (keys) and frequencies (values). - """ - if isinstance(self.phases, dict): - counts = self.phases - if as_float: - phases = { - _bit_string_to_phase(k): counts[k] for k in counts.keys() if counts[k] > cutoff - } - else: - phases = {k: counts[k] for k in counts.keys() if counts[k] > cutoff} - - else: - phases = {} - for idx, amplitude in enumerate(self.phases): - if amplitude > cutoff: - # Each index corresponds to a computational basis state with the LSB rightmost. - # But, we chose to apply the unitaries such that the phase is recorded - # in reverse order. So, we reverse the bitstrings here. - binary_phase_string = numpy.binary_repr(idx, self._num_evaluation_qubits)[::-1] - if as_float: - _key: str | float = _bit_string_to_phase(binary_phase_string) - else: - _key = binary_phase_string - phases[_key] = amplitude - - phases = _sort_phases(phases) - - return phases - - -def _bit_string_to_phase(binary_string: str) -> float: - """Convert bit string to a normalized phase in :math:`[0,1)`. - - It is assumed that the bit string is correctly padded and that the order of - the bits has been reversed relative to their order when the counts - were recorded. The LSB is the right most when interpreting the bitstring as - a phase. - - Args: - binary_string: A string of characters '0' and '1'. - - Returns: - A phase scaled to :math:`[0,1)`. - """ - n_qubits = len(binary_string) - return int(binary_string, 2) / (2**n_qubits) - - -def _sort_phases(phases: dict) -> dict: - """Sort a dict of bit strings representing phases (keys) and frequencies (values) by bit string. - - The bit strings are sorted according to increasing phase. This relies on Python - preserving insertion order when building dicts. - """ - pkeys = list(phases.keys()) - pkeys.sort(reverse=False) # Sorts in order of the integer encoded by binary string - phases = {k: phases[k] for k in pkeys} - return phases diff --git a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py b/qiskit/algorithms/phase_estimators/phase_estimation_scale.py deleted file mode 100644 index e22b3e18cd9e..000000000000 --- a/qiskit/algorithms/phase_estimators/phase_estimation_scale.py +++ /dev/null @@ -1,160 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Scaling for Hamiltonian and eigenvalues to avoid phase wrapping""" -from __future__ import annotations -import numpy as np - -from qiskit.opflow import SummedOp, PauliSumOp -from qiskit.quantum_info import SparsePauliOp, Operator -from qiskit.quantum_info.operators.base_operator import BaseOperator - - -class PhaseEstimationScale: - """Set and use a bound on eigenvalues of a Hermitian operator in order to ensure phases are in - the desired range and to convert measured phases into eigenvectors. - - The ``bound`` is set when constructing this class. Then the method ``scale`` is used to find the - factor by which to scale the operator. - - If ``bound`` is equal exactly to the largest eigenvalue, and the smallest eigenvalue is minus - the largest, then these two eigenvalues will not be distinguished. For example, if the Hermitian - operator is the Pauli Z operator with eigenvalues :math:`1` and :math:`-1`, and ``bound`` is - :math:`1`, then both eigenvalues will be mapped to :math:`1`. - This can be avoided by making ``bound`` a bit larger. - - Increasing ``bound`` decreases the part of the interval :math:`[0, 1)` that is used to map - eigenvalues to ``phi``. However, sometimes this results in a better determination of the - eigenvalues, because 1) although there are fewer discrete phases in the useful range, it may - shift one of the discrete phases closer to the actual phase. And, 2) If one of the discrete - phases is close to, or exactly equal to the actual phase, then artifacts (probability) in - neighboring phases will be reduced. This is important because the artifacts may be larger than - the probability in a phase representing another eigenvalue of interest whose corresponding - eigenstate has a relatively small weight in the input state. - - """ - - def __init__(self, bound: float) -> None: - """ - Args: - bound: an upper bound on the absolute value of the eigenvalues of a Hermitian operator. - (The operator is not needed here.) - """ - self._bound = bound - - @property - def scale(self) -> float: - r"""Return the Hamiltonian scaling factor. - - Return the scale factor by which a Hermitian operator must be multiplied - so that the phase of the corresponding unitary is restricted to :math:`[-\pi, \pi]`. - This factor is computed from the bound on the absolute values of the eigenvalues - of the operator. The methods ``scale_phase`` and ``scale_phases`` are used recover - the eigenvalues corresponding the original (unscaled) Hermitian operator. - - Returns: - The scale factor. - """ - return np.pi / self._bound - - def scale_phase(self, phi: float, id_coefficient: float = 0.0) -> float: - r"""Convert a phase into an eigenvalue. - - The input phase ``phi`` corresponds to the eigenvalue of a unitary obtained by - exponentiating a scaled Hermitian operator. Recall that the phase - is obtained from ``phi`` as :math:`2\pi\phi`. Furthermore, the Hermitian operator - was scaled so that ``phi`` is restricted to :math:`[-1/2, 1/2]`, corresponding to - phases in :math:`[-\pi, \pi]`. But the values of `phi` read from the phase-readout - register are in :math:`[0, 1)`. Any value of ``phi`` greater than :math:`1/2` corresponds - to a raw phase of minus the complement with respect to 1. After this possible - shift, the phase is scaled by the inverse of the factor by which the - Hermitian operator was scaled to recover the eigenvalue of the Hermitian - operator. - - Args: - phi: Normalized phase in :math:`[0, 1)` to be converted to an eigenvalue. - id_coefficient: All eigenvalues are shifted by this value. - - Returns: - An eigenvalue computed from the input phase. - """ - w = 2 * self._bound - if phi <= 0.5: - return phi * w + id_coefficient - else: - return (phi - 1) * w + id_coefficient - - def scale_phases(self, phases: list | dict, id_coefficient: float = 0.0) -> dict | list: - """Convert a list or dict of phases to eigenvalues. - - The values in the list, or keys in the dict, are values of ``phi` and - are converted as described in the description of ``scale_phase``. In case - ``phases`` is a dict, the values of the dict are passed unchanged. - - Args: - phases: a list or dict of values of ``phi``. - id_coefficient: All eigenvalues are shifted by this value. - - Returns: - Eigenvalues computed from phases. - """ - if isinstance(phases, list): - phases = [self.scale_phase(x, id_coefficient) for x in phases] - else: - phases = {self.scale_phase(x, id_coefficient): phases[x] for x in phases.keys()} - - return phases - - @classmethod - def from_pauli_sum( - cls, pauli_sum: SummedOp | PauliSumOp | SparsePauliOp | Operator - ) -> "PhaseEstimationScale" | float: - """Create a PhaseEstimationScale from a `SummedOp` representing a sum of Pauli Operators. - - It is assumed that the ``pauli_sum`` is the sum of ``PauliOp`` objects. The bound on - the absolute value of the eigenvalues of the sum is obtained as the sum of the - absolute values of the coefficients of the terms. This is the best bound available in - the generic case. A ``PhaseEstimationScale`` object is instantiated using this bound. - - Args: - pauli_sum: A ``SummedOp`` whose terms are ``PauliOp`` objects. - - Raises: - ValueError: if ``pauli_sum`` is not a sum of Pauli operators. - - Returns: - A ``PhaseEstimationScale`` object - """ - if isinstance(pauli_sum, PauliSumOp): - bound = abs(pauli_sum.coeff) * sum(abs(coeff) for coeff in pauli_sum.coeffs) - return PhaseEstimationScale(bound) - elif isinstance(pauli_sum, SparsePauliOp): - bound = sum(abs(coeff) for coeff in pauli_sum.coeffs) - return PhaseEstimationScale(bound) - elif isinstance(pauli_sum, Operator): - bound = np.sum(np.abs(np.linalg.eigvalsh(pauli_sum))) - return PhaseEstimationScale(bound) - elif isinstance(pauli_sum, BaseOperator): - raise ValueError( - f"For the operator of type {type(pauli_sum)} the bound needs to be provided in the " - f"algorithm." - ) - else: - if pauli_sum.primitive_strings() != {"Pauli"}: - raise ValueError( - "`pauli_sum` must be a sum of Pauli operators. Got primitives {}.".format( - pauli_sum.primitive_strings() - ) - ) - - bound = abs(pauli_sum.coeff) * sum(abs(pauli.coeff) for pauli in pauli_sum) - return PhaseEstimationScale(bound) diff --git a/qiskit/algorithms/phase_estimators/phase_estimator.py b/qiskit/algorithms/phase_estimators/phase_estimator.py deleted file mode 100644 index 09f8113e5f4a..000000000000 --- a/qiskit/algorithms/phase_estimators/phase_estimator.py +++ /dev/null @@ -1,58 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Phase Estimator interface.""" - -from __future__ import annotations -from abc import ABC, abstractmethod -from qiskit.circuit import QuantumCircuit -from qiskit.algorithms.algorithm_result import AlgorithmResult - - -class PhaseEstimator(ABC): - """The Phase Estimator interface. - - Algorithms that can compute a phase for a unitary operator and initial state may implement this - interface to allow different algorithms to be used interchangeably. - - The phase returned is a canonical phase determined by the specific algorithm, such as the most - likely phase. In addition, the algorithm may provide an interface to retrieve phases by other - criteria. - """ - - @abstractmethod - def estimate( - self, - unitary: QuantumCircuit, - state_preparation: QuantumCircuit | None = None, - ) -> "PhaseEstimatorResult": - """Estimate the phase.""" - raise NotImplementedError - - @staticmethod - def _get_reversed_bitstring(length: int, number: int) -> str: - return f"{number:b}".zfill(length)[::-1] - - -class PhaseEstimatorResult(AlgorithmResult): - """Phase Estimator Result.""" - - @property - @abstractmethod - def phase(self) -> float: - r"""Return the estimated phase as a number in :math:`[0.0, 1.0)`. - - 1.0 corresponds to a phase of :math:`2\pi`. In case the phase estimation algorithm - computes more than one phase, this attribute returns a canonical single phase; for - example, the most likely phase. - """ - raise NotImplementedError diff --git a/qiskit/algorithms/state_fidelities/__init__.py b/qiskit/algorithms/state_fidelities/__init__.py deleted file mode 100644 index ea8e4e03bf89..000000000000 --- a/qiskit/algorithms/state_fidelities/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -===================================================================== -State Fidelity Interfaces (:mod:`qiskit.algorithms.state_fidelities`) -===================================================================== - -.. currentmodule:: qiskit.algorithms.state_fidelities - -State Fidelities -================ - -.. autosummary:: - :toctree: ../stubs/ - - BaseStateFidelity - ComputeUncompute - -Results -======= - - .. autosummary:: - :toctree: ../stubs/ - - StateFidelityResult - -""" - -from .base_state_fidelity import BaseStateFidelity -from .compute_uncompute import ComputeUncompute -from .state_fidelity_result import StateFidelityResult - -__all__ = ["BaseStateFidelity", "ComputeUncompute", "StateFidelityResult"] diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py deleted file mode 100644 index 9395889bc5da..000000000000 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ /dev/null @@ -1,308 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -Base state fidelity interface -""" - -from __future__ import annotations -from abc import ABC, abstractmethod -from collections.abc import Sequence, Mapping -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import ParameterVector - -from ..algorithm_job import AlgorithmJob -from .state_fidelity_result import StateFidelityResult - - -class BaseStateFidelity(ABC): - r""" - An interface to calculate state fidelities (state overlaps) for pairs of - (parametrized) quantum circuits. The calculation depends on the particular - fidelity method implementation, but can be always defined as the state overlap: - - .. math:: - - |\langle\psi(x)|\phi(y)\rangle|^2 - - where :math:`x` and :math:`y` are optional parametrizations of the - states :math:`\psi` and :math:`\phi` prepared by the circuits - ``circuit_1`` and ``circuit_2``, respectively. - - """ - - def __init__(self) -> None: - - # use cache for preventing unnecessary circuit compositions - self._circuit_cache: Mapping[tuple[int, int], QuantumCircuit] = {} - - @staticmethod - def _preprocess_values( - circuits: QuantumCircuit | Sequence[QuantumCircuit], - values: Sequence[float] | Sequence[Sequence[float]] | None = None, - ) -> Sequence[Sequence[float]]: - """ - Checks whether the passed values match the shape of the parameters - of the corresponding circuits and formats values to 2D list. - - Args: - circuits: List of circuits to be checked. - values: Parameter values corresponding to the circuits to be checked. - - Returns: - A 2D value list if the values match the circuits, or an empty 2D list - if values is None. - - Raises: - ValueError: if the number of parameter values doesn't match the number of - circuit parameters - TypeError: if the input values are not a sequence. - """ - - if isinstance(circuits, QuantumCircuit): - circuits = [circuits] - - if values is None: - for circuit in circuits: - if circuit.num_parameters != 0: - raise ValueError( - f"`values` cannot be `None` because circuit <{circuit.name}> has " - f"{circuit.num_parameters} free parameters." - ) - return [[]] - else: - - # Support ndarray - if isinstance(values, np.ndarray): - values = values.tolist() - if len(values) > 0 and isinstance(values[0], np.ndarray): - values = [v.tolist() for v in values] - - if not isinstance(values, Sequence): - raise TypeError( - f"Expected a sequence of numerical parameter values, " - f"but got input type {type(values)} instead." - ) - - # ensure 2d - if len(values) > 0 and not isinstance(values[0], Sequence) or len(values) == 0: - values = [values] - - return values - - def _check_qubits_match(self, circuit_1: QuantumCircuit, circuit_2: QuantumCircuit) -> None: - """ - Checks that the number of qubits of 2 circuits matches. - Args: - circuit_1: (Parametrized) quantum circuit. - circuit_2: (Parametrized) quantum circuit. - - Raises: - ValueError: when ``circuit_1`` and ``circuit_2`` don't have the - same number of qubits. - """ - - if circuit_1.num_qubits != circuit_2.num_qubits: - raise ValueError( - f"The number of qubits for the first circuit ({circuit_1.num_qubits}) " - f"and second circuit ({circuit_2.num_qubits}) are not the same." - ) - - @abstractmethod - def create_fidelity_circuit( - self, circuit_1: QuantumCircuit, circuit_2: QuantumCircuit - ) -> QuantumCircuit: - """ - Implementation-dependent method to create a fidelity circuit - from 2 circuit inputs. - - Args: - circuit_1: (Parametrized) quantum circuit. - circuit_2: (Parametrized) quantum circuit. - - Returns: - The fidelity quantum circuit corresponding to ``circuit_1`` and ``circuit_2``. - """ - raise NotImplementedError - - def _construct_circuits( - self, - circuits_1: QuantumCircuit | Sequence[QuantumCircuit], - circuits_2: QuantumCircuit | Sequence[QuantumCircuit], - ) -> Sequence[QuantumCircuit]: - """ - Constructs the list of fidelity circuits to be evaluated. - These circuits represent the state overlap between pairs of input circuits, - and their construction depends on the fidelity method implementations. - - Args: - circuits_1: (Parametrized) quantum circuits. - circuits_2: (Parametrized) quantum circuits. - - Returns: - List of constructed fidelity circuits. - - Raises: - ValueError: if the length of the input circuit lists doesn't match. - """ - - if isinstance(circuits_1, QuantumCircuit): - circuits_1 = [circuits_1] - if isinstance(circuits_2, QuantumCircuit): - circuits_2 = [circuits_2] - - if len(circuits_1) != len(circuits_2): - raise ValueError( - f"The length of the first circuit list({len(circuits_1)}) " - f"and second circuit list ({len(circuits_2)}) is not the same." - ) - - circuits = [] - for (circuit_1, circuit_2) in zip(circuits_1, circuits_2): - - # TODO: improve caching, what if the circuit is modified without changing the id? - circuit = self._circuit_cache.get((id(circuit_1), id(circuit_2))) - - if circuit is not None: - circuits.append(circuit) - else: - self._check_qubits_match(circuit_1, circuit_2) - - # re-parametrize input circuits - # TODO: make smarter checks to avoid unnecesary reparametrizations - parameters_1 = ParameterVector("x", circuit_1.num_parameters) - parametrized_circuit_1 = circuit_1.assign_parameters(parameters_1) - parameters_2 = ParameterVector("y", circuit_2.num_parameters) - parametrized_circuit_2 = circuit_2.assign_parameters(parameters_2) - - circuit = self.create_fidelity_circuit( - parametrized_circuit_1, parametrized_circuit_2 - ) - circuits.append(circuit) - # update cache - self._circuit_cache[id(circuit_1), id(circuit_2)] = circuit - - return circuits - - def _construct_value_list( - self, - circuits_1: Sequence[QuantumCircuit], - circuits_2: Sequence[QuantumCircuit], - values_1: Sequence[float] | Sequence[Sequence[float]] | None = None, - values_2: Sequence[float] | Sequence[Sequence[float]] | None = None, - ) -> list[float]: - """ - Preprocesses input parameter values to match the fidelity - circuit parametrization, and return in list format. - - Args: - circuits_1: (Parametrized) quantum circuits preparing the - first list of quantum states. - circuits_2: (Parametrized) quantum circuits preparing the - second list of quantum states. - values_1: Numerical parameters to be bound to the first circuits. - values_2: Numerical parameters to be bound to the second circuits. - - Returns: - List of parameter values for fidelity circuit. - - """ - values_1 = self._preprocess_values(circuits_1, values_1) - values_2 = self._preprocess_values(circuits_2, values_2) - - values = [] - if len(values_2[0]) == 0: - values = list(values_1) - elif len(values_1[0]) == 0: - values = list(values_2) - else: - for (val_1, val_2) in zip(values_1, values_2): - values.append(val_1 + val_2) - - return values - - @abstractmethod - def _run( - self, - circuits_1: QuantumCircuit | Sequence[QuantumCircuit], - circuits_2: QuantumCircuit | Sequence[QuantumCircuit], - values_1: Sequence[float] | Sequence[Sequence[float]] | None = None, - values_2: Sequence[float] | Sequence[Sequence[float]] | None = None, - **options, - ) -> StateFidelityResult: - r""" - Computes the state overlap (fidelity) calculation between two - (parametrized) circuits (first and second) for a specific set of parameter - values (first and second). - - Args: - circuits_1: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. - circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. - values_1: Numerical parameters to be bound to the first set of circuits - values_2: Numerical parameters to be bound to the second set of circuits. - options: Primitive backend runtime options used for circuit execution. The order - of priority is\: options in ``run`` method > fidelity's default - options > primitive's default setting. - Higher priority setting overrides lower priority setting. - - Returns: - The result of the fidelity calculation. - """ - raise NotImplementedError - - def run( - self, - circuits_1: QuantumCircuit | Sequence[QuantumCircuit], - circuits_2: QuantumCircuit | Sequence[QuantumCircuit], - values_1: Sequence[float] | Sequence[Sequence[float]] | None = None, - values_2: Sequence[float] | Sequence[Sequence[float]] | None = None, - **options, - ) -> AlgorithmJob: - r""" - Runs asynchronously the state overlap (fidelity) calculation between two - (parametrized) circuits (first and second) for a specific set of parameter - values (first and second). This calculation depends on the particular - fidelity method implementation. - - Args: - circuits_1: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. - circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. - values_1: Numerical parameters to be bound to the first set of circuits. - values_2: Numerical parameters to be bound to the second set of circuits. - options: Primitive backend runtime options used for circuit execution. The order - of priority is\: options in ``run`` method > fidelity's default - options > primitive's default setting. - Higher priority setting overrides lower priority setting. - - Returns: - Primitive job for the fidelity calculation. - The job's result is an instance of ``StateFidelityResult``. - """ - - job = AlgorithmJob(self._run, circuits_1, circuits_2, values_1, values_2, **options) - - job.submit() - return job - - def _truncate_fidelities(self, fidelities: Sequence[float]) -> Sequence[float]: - """ - Ensures fidelity result in [0,1]. - - Args: - fidelities: Sequence of raw fidelity results. - - Returns: - List of truncated fidelities. - - """ - return np.clip(fidelities, 0, 1).tolist() diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py deleted file mode 100644 index be0879fadc27..000000000000 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ /dev/null @@ -1,249 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -Compute-uncompute fidelity interface using primitives -""" - -from __future__ import annotations -from collections.abc import Sequence -from copy import copy - -from qiskit import QuantumCircuit -from qiskit.primitives import BaseSampler -from qiskit.providers import Options - -from ..exceptions import AlgorithmError -from .base_state_fidelity import BaseStateFidelity -from .state_fidelity_result import StateFidelityResult - - -class ComputeUncompute(BaseStateFidelity): - r""" - This class leverages the sampler primitive to calculate the state - fidelity of two quantum circuits following the compute-uncompute - method (see [1] for further reference). - The fidelity can be defined as the state overlap. - - .. math:: - - |\langle\psi(x)|\phi(y)\rangle|^2 - - where :math:`x` and :math:`y` are optional parametrizations of the - states :math:`\psi` and :math:`\phi` prepared by the circuits - ``circuit_1`` and ``circuit_2``, respectively. - - **Reference:** - [1] Havlíček, V., Córcoles, A. D., Temme, K., Harrow, A. W., Kandala, - A., Chow, J. M., & Gambetta, J. M. (2019). Supervised learning - with quantum-enhanced feature spaces. Nature, 567(7747), 209-212. - `arXiv:1804.11326v2 [quant-ph] `_ - - """ - - def __init__( - self, - sampler: BaseSampler, - options: Options | None = None, - local: bool = False, - ) -> None: - r""" - Args: - sampler: Sampler primitive instance. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > fidelity's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting. - local: If set to ``True``, the fidelity is averaged over - single-qubit projectors - - .. math:: - - \hat{O} = \frac{1}{N}\sum_{i=1}^N|0_i\rangle\langle 0_i|, - - instead of the global projector :math:`|0\rangle\langle 0|^{\otimes n}`. - This coincides with the standard (global) fidelity in the limit of - the fidelity approaching 1. Might be used to increase the variance - to improve trainability in algorithms such as :class:`~.time_evolvers.PVQD`. - - Raises: - ValueError: If the sampler is not an instance of ``BaseSampler``. - """ - if not isinstance(sampler, BaseSampler): - raise ValueError( - f"The sampler should be an instance of BaseSampler, " f"but got {type(sampler)}" - ) - self._sampler: BaseSampler = sampler - self._local = local - self._default_options = Options() - if options is not None: - self._default_options.update_options(**options) - super().__init__() - - def create_fidelity_circuit( - self, circuit_1: QuantumCircuit, circuit_2: QuantumCircuit - ) -> QuantumCircuit: - """ - Combines ``circuit_1`` and ``circuit_2`` to create the - fidelity circuit following the compute-uncompute method. - - Args: - circuit_1: (Parametrized) quantum circuit. - circuit_2: (Parametrized) quantum circuit. - - Returns: - The fidelity quantum circuit corresponding to circuit_1 and circuit_2. - """ - if len(circuit_1.clbits) > 0: - circuit_1.remove_final_measurements() - if len(circuit_2.clbits) > 0: - circuit_2.remove_final_measurements() - - circuit = circuit_1.compose(circuit_2.inverse()) - circuit.measure_all() - return circuit - - def _run( - self, - circuits_1: QuantumCircuit | Sequence[QuantumCircuit], - circuits_2: QuantumCircuit | Sequence[QuantumCircuit], - values_1: Sequence[float] | Sequence[Sequence[float]] | None = None, - values_2: Sequence[float] | Sequence[Sequence[float]] | None = None, - **options, - ) -> StateFidelityResult: - r""" - Computes the state overlap (fidelity) calculation between two - (parametrized) circuits (first and second) for a specific set of parameter - values (first and second) following the compute-uncompute method. - - Args: - circuits_1: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. - circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. - values_1: Numerical parameters to be bound to the first circuits. - values_2: Numerical parameters to be bound to the second circuits. - options: Primitive backend runtime options used for circuit execution. - The order of priority is: options in ``run`` method > fidelity's - default options > primitive's default setting. - Higher priority setting overrides lower priority setting. - - Returns: - The result of the fidelity calculation. - - Raises: - ValueError: At least one pair of circuits must be defined. - AlgorithmError: If the sampler job is not completed successfully. - """ - - circuits = self._construct_circuits(circuits_1, circuits_2) - if len(circuits) == 0: - raise ValueError( - "At least one pair of circuits must be defined to calculate the state overlap." - ) - values = self._construct_value_list(circuits_1, circuits_2, values_1, values_2) - - # The priority of run options is as follows: - # options in `evaluate` method > fidelity's default options > - # primitive's default options. - opts = copy(self._default_options) - opts.update_options(**options) - - job = self._sampler.run(circuits=circuits, parameter_values=values, **opts.__dict__) - - try: - result = job.result() - except Exception as exc: - raise AlgorithmError("Sampler job failed!") from exc - - if self._local: - raw_fidelities = [ - self._get_local_fidelity(prob_dist, circuit.num_qubits) - for prob_dist, circuit in zip(result.quasi_dists, circuits) - ] - else: - raw_fidelities = [ - self._get_global_fidelity(prob_dist) for prob_dist in result.quasi_dists - ] - fidelities = self._truncate_fidelities(raw_fidelities) - - return StateFidelityResult( - fidelities=fidelities, - raw_fidelities=raw_fidelities, - metadata=result.metadata, - options=self._get_local_options(opts.__dict__), - ) - - @property - def options(self) -> Options: - """Return the union of estimator options setting and fidelity default options, - where, if the same field is set in both, the fidelity's default options override - the primitive's default setting. - - Returns: - The fidelity default + estimator options. - """ - return self._get_local_options(self._default_options.__dict__) - - def update_default_options(self, **options): - """Update the fidelity's default options setting. - - Args: - **options: The fields to update the default options. - """ - - self._default_options.update_options(**options) - - def _get_local_options(self, options: Options) -> Options: - """Return the union of the primitive's default setting, - the fidelity default options, and the options in the ``run`` method. - The order of priority is: options in ``run`` method > fidelity's - default options > primitive's default setting. - - Args: - options: The fields to update the options - - Returns: - The fidelity default + estimator + run options. - """ - opts = copy(self._sampler.options) - opts.update_options(**options) - return opts - - def _get_global_fidelity(self, probability_distribution: dict[int, float]) -> float: - """Process the probability distribution of a measurement to determine the - global fidelity. - - Args: - probability_distribution: Obtained from the measurement result - - Returns: - The global fidelity. - """ - return probability_distribution.get(0, 0) - - def _get_local_fidelity( - self, probability_distribution: dict[int, float], num_qubits: int - ) -> float: - """Process the probability distribution of a measurement to determine the - local fidelity by averaging over single-qubit projectors. - - Args: - probability_distribution: Obtained from the measurement result - - Returns: - The local fidelity. - """ - fidelity = 0.0 - for qubit in range(num_qubits): - for bitstring, prob in probability_distribution.items(): - # Check whether the bit representing the current qubit is 0 - if not bitstring >> qubit & 1: - fidelity += prob / num_qubits - return fidelity diff --git a/qiskit/algorithms/state_fidelities/state_fidelity_result.py b/qiskit/algorithms/state_fidelities/state_fidelity_result.py deleted file mode 100644 index 88dca035f94c..000000000000 --- a/qiskit/algorithms/state_fidelities/state_fidelity_result.py +++ /dev/null @@ -1,37 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -Fidelity result class -""" - -from __future__ import annotations - -from collections.abc import Sequence, Mapping -from typing import Any -from dataclasses import dataclass - -from qiskit.providers import Options - - -@dataclass(frozen=True) -class StateFidelityResult: - """This class stores the result of StateFidelity computations.""" - - fidelities: Sequence[float] - """List of truncated fidelity values for each pair of input circuits, ensured to be in [0,1].""" - raw_fidelities: Sequence[float] - """List of raw fidelity values for each pair of input circuits, which might not be in [0,1] - depending on the error mitigation method used.""" - metadata: Sequence[Mapping[str, Any]] - """Additional information about the fidelity calculation.""" - options: Options - """Primitive runtime options for the execution of the fidelity job.""" diff --git a/qiskit/algorithms/time_evolvers/__init__.py b/qiskit/algorithms/time_evolvers/__init__.py deleted file mode 100644 index c2ad1fe7ec26..000000000000 --- a/qiskit/algorithms/time_evolvers/__init__.py +++ /dev/null @@ -1,38 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Quantum Time Evolution package.""" - -from .imaginary_time_evolver import ImaginaryTimeEvolver -from .real_time_evolver import RealTimeEvolver -from .time_evolution_problem import TimeEvolutionProblem -from .time_evolution_result import TimeEvolutionResult -from .trotterization import TrotterQRTE -from .pvqd import PVQD, PVQDResult -from .classical_methods import SciPyImaginaryEvolver, SciPyRealEvolver -from .variational import VarQITE, VarQRTE, VarQTE, VarQTEResult - -__all__ = [ - "ImaginaryTimeEvolver", - "RealTimeEvolver", - "TimeEvolutionProblem", - "TimeEvolutionResult", - "TrotterQRTE", - "PVQD", - "PVQDResult", - "SciPyImaginaryEvolver", - "SciPyRealEvolver", - "VarQITE", - "VarQRTE", - "VarQTE", - "VarQTEResult", -] diff --git a/qiskit/algorithms/time_evolvers/classical_methods/__init__.py b/qiskit/algorithms/time_evolvers/classical_methods/__init__.py deleted file mode 100644 index 266b349fbef7..000000000000 --- a/qiskit/algorithms/time_evolvers/classical_methods/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Classical Methods for Quantum Time Evolution package.""" - -from .scipy_real_evolver import SciPyRealEvolver -from .scipy_imaginary_evolver import SciPyImaginaryEvolver - -__all__ = ["SciPyRealEvolver", "SciPyImaginaryEvolver"] diff --git a/qiskit/algorithms/time_evolvers/classical_methods/evolve.py b/qiskit/algorithms/time_evolvers/classical_methods/evolve.py deleted file mode 100644 index 18c679a03c8f..000000000000 --- a/qiskit/algorithms/time_evolvers/classical_methods/evolve.py +++ /dev/null @@ -1,219 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""Auxiliary functions for SciPy Time Evolvers""" -from __future__ import annotations -import logging -from scipy.sparse import csr_matrix -from scipy.sparse.linalg import expm_multiply -import numpy as np - -from qiskit.quantum_info.states import Statevector -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from qiskit import QuantumCircuit -from qiskit.opflow import PauliSumOp -from ..time_evolution_problem import TimeEvolutionProblem -from ..time_evolution_result import TimeEvolutionResult -from ...exceptions import AlgorithmError - -from ...list_or_dict import ListOrDict - -logger = logging.getLogger(__name__) - - -def _create_observable_output( - ops_ev_mean: np.ndarray, - evolution_problem: TimeEvolutionProblem, -) -> tuple[ListOrDict[tuple[np.ndarray, np.ndarray]], np.ndarray]: - """Creates the right output format for the evaluated auxiliary operators. - Args: - ops_ev_mean: Array containing the expectation value of each observable at each timestep. - evolution_problem: Time Evolution Problem to create the output of. - - Returns: - An output with the observables mean value at the appropriate times depending on whether - the auxiliary operators in the time evolution problem are a `list` or a `dict`. - - """ - - aux_ops = evolution_problem.aux_operators - - time_array = np.linspace(0, evolution_problem.time, ops_ev_mean.shape[-1]) - zero_array = np.zeros(ops_ev_mean.shape[-1]) # std=0 since it is an exact method - - operators_number = 0 if aux_ops is None else len(aux_ops) - - observable_evolution = [(ops_ev_mean[i], zero_array) for i in range(operators_number)] - - if isinstance(aux_ops, dict): - observable_evolution = dict(zip(aux_ops.keys(), observable_evolution)) - - return observable_evolution, time_array - - -def _create_obs_final( - ops_ev_mean: np.ndarray, - evolution_problem: TimeEvolutionProblem, -) -> ListOrDict[tuple[complex, complex]]: - """Creates the right output format for the final value of the auxiliary operators. - - Args: - ops_ev_mean: Array containing the expectation value of each observable at the final timestep. - evolution_problem: Evolution problem to create the output of. - - Returns: - An output with the observables mean value at the appropriate times depending on whether - the auxiliary operators in the evolution problem are a `list` or a `dict`. - - """ - - aux_ops = evolution_problem.aux_operators - aux_ops_evaluated: ListOrDict[tuple[complex, complex]] = [(op_ev, 0) for op_ev in ops_ev_mean] - if isinstance(aux_ops, dict): - aux_ops_evaluated = dict(zip(aux_ops.keys(), aux_ops_evaluated)) - return aux_ops_evaluated - - -def _evaluate_aux_ops( - aux_ops: list[csr_matrix], - state: np.ndarray, -) -> np.ndarray: - """Evaluates the aux operators if they are provided and stores their value. - - Returns: - Mean of the aux operators for a given state. - """ - op_means = np.array([np.real(state.conjugate().dot(op.dot(state))) for op in aux_ops]) - return op_means - - -def _operator_to_matrix(operator: BaseOperator | PauliSumOp): - - if isinstance(operator, PauliSumOp): - op_matrix = operator.to_spmatrix() - else: - try: - op_matrix = operator.to_matrix(sparse=True) - except TypeError: - logger.debug( - "WARNING: operator of type `%s` does not support sparse matrices. " - "Trying dense computation", - type(operator), - ) - try: - op_matrix = operator.to_matrix() - except AttributeError as ex: - raise AlgorithmError(f"Unsupported operator type `{type(operator)}`.") from ex - return op_matrix - - -def _build_scipy_operators( - evolution_problem: TimeEvolutionProblem, num_timesteps: int, real_time: bool -) -> tuple[np.ndarray, list[csr_matrix], csr_matrix]: - """Returns the matrices and parameters needed for time evolution in the appropriate format. - - Args: - evolution_problem: The definition of the evolution problem. - num_timesteps: Number of timesteps to be performed. - real_time: If `True`, returned operators will correspond to real time evolution, - Else, they will correspond to imaginary time evolution. - - Returns: - A tuple with the initial state, the list of operators to evaluate and the operator to be - exponentiated to perform one timestep. - - Raises: - ValueError: If the Hamiltonian can not be converted into a sparse matrix or dense matrix. - """ - # Convert the initial state and Hamiltonian into sparse matrices. - if isinstance(evolution_problem.initial_state, QuantumCircuit): - state = Statevector(evolution_problem.initial_state).data - else: - state = evolution_problem.initial_state.data - - hamiltonian = _operator_to_matrix(operator=evolution_problem.hamiltonian) - - if isinstance(evolution_problem.aux_operators, list): - aux_ops = [ - _operator_to_matrix(operator=aux_op) for aux_op in evolution_problem.aux_operators - ] - elif isinstance(evolution_problem.aux_operators, dict): - aux_ops = [ - _operator_to_matrix(operator=aux_op) - for aux_op in evolution_problem.aux_operators.values() - ] - else: - aux_ops = [] - timestep = evolution_problem.time / num_timesteps - step_operator = -((1.0j) ** real_time) * timestep * hamiltonian - return state, aux_ops, step_operator - - -def _evolve( - evolution_problem: TimeEvolutionProblem, num_timesteps: int, real_time: bool -) -> TimeEvolutionResult: - r"""Performs either real or imaginary time evolution :math:`\exp(-i t H)|\Psi\rangle`. - - Args: - evolution_problem: The definition of the evolution problem. - num_timesteps: Number of timesteps to be performed. - real_time: If `True`, returned operators will correspond to real time evolution, - Else, they will correspond to imaginary time evolution. - - Returns: - Evolution result which includes an evolved quantum state. - - Raises: - ValueError: If the Hamiltonian is time dependent. - ValueError: If the initial state is `None`. - - """ - if num_timesteps <= 0: - raise ValueError("Variable `num_timesteps` needs to be a positive integer.") - - if evolution_problem.t_param is not None: - raise ValueError("Time dependent Hamiltonians are not supported.") - - if evolution_problem.initial_state is None: - raise ValueError("Initial state is `None`") - - state, aux_ops, step_operator = _build_scipy_operators( - evolution_problem=evolution_problem, num_timesteps=num_timesteps, real_time=real_time - ) - - # Create empty arrays to store the time evolution of the aux operators. - number_operators = ( - 0 if evolution_problem.aux_operators is None else len(evolution_problem.aux_operators) - ) - ops_ev_mean = np.empty(shape=(number_operators, num_timesteps + 1), dtype=complex) - - renormalize = ( - (lambda state: state) if real_time else (lambda state: state / np.linalg.norm(state)) - ) - - # Perform the time evolution and stores the value of the operators at each timestep. - for ts in range(num_timesteps): - ops_ev_mean[:, ts] = _evaluate_aux_ops(aux_ops, state) - state = expm_multiply(A=step_operator, B=state) - state = renormalize(state) - - ops_ev_mean[:, num_timesteps] = _evaluate_aux_ops(aux_ops, state) - - observable_history, times = _create_observable_output(ops_ev_mean, evolution_problem) - aux_ops_evaluated = _create_obs_final(ops_ev_mean[:, -1], evolution_problem) - - return TimeEvolutionResult( - evolved_state=Statevector(state), - aux_ops_evaluated=aux_ops_evaluated, - observables=observable_history, - times=times, - ) diff --git a/qiskit/algorithms/time_evolvers/classical_methods/scipy_imaginary_evolver.py b/qiskit/algorithms/time_evolvers/classical_methods/scipy_imaginary_evolver.py deleted file mode 100644 index f181da10f436..000000000000 --- a/qiskit/algorithms/time_evolvers/classical_methods/scipy_imaginary_evolver.py +++ /dev/null @@ -1,51 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Classical Quantum Imaginary Time Evolution.""" - -from ..time_evolution_problem import TimeEvolutionProblem -from ..time_evolution_result import TimeEvolutionResult -from ..imaginary_time_evolver import ImaginaryTimeEvolver -from .evolve import _evolve - - -class SciPyImaginaryEvolver(ImaginaryTimeEvolver): - r"""Classical Evolver for imaginary time evolution. - - Evolves an initial state :math:`|\Psi\rangle` for an imaginary time :math:`\tau = it` - under a Hamiltonian :math:`H`, as provided in the ``evolution_problem``. - Note that the precision of the evolver does not depend on the number of - timesteps taken. - """ - - def __init__(self, num_timesteps: int): - r""" - Args: - num_timesteps: The number of timesteps in the simulation. - Raises: - ValueError: If `num_timesteps` is not a positive integer. - """ - self.num_timesteps = num_timesteps - - def evolve(self, evolution_problem: TimeEvolutionProblem) -> TimeEvolutionResult: - r"""Perform imaginary time evolution :math:`\exp(-\tau H)|\Psi\rangle`. - - Evolves an initial state :math:`|\Psi\rangle` for an imaginary time :math:`\tau` - under a Hamiltonian :math:`H`, as provided in the ``evolution_problem``. - - Args: - evolution_problem: The definition of the evolution problem. - - Returns: - Evolution result which includes an evolved quantum state. - """ - return _evolve(evolution_problem, self.num_timesteps, real_time=False) diff --git a/qiskit/algorithms/time_evolvers/classical_methods/scipy_real_evolver.py b/qiskit/algorithms/time_evolvers/classical_methods/scipy_real_evolver.py deleted file mode 100644 index b01c16205bfd..000000000000 --- a/qiskit/algorithms/time_evolvers/classical_methods/scipy_real_evolver.py +++ /dev/null @@ -1,50 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Classical Quantum Real Time Evolution.""" -from .evolve import _evolve -from ..time_evolution_problem import TimeEvolutionProblem -from ..time_evolution_result import TimeEvolutionResult -from ..real_time_evolver import RealTimeEvolver - - -class SciPyRealEvolver(RealTimeEvolver): - r"""Classical Evolver for real time evolution. - - Evolves an initial state :math:`|\Psi\rangle` for a time :math:`t` - under a Hamiltonian :math:`H`, as provided in the ``evolution_problem``. - Note that the precision of the evolver does not depend on the number of - timesteps taken. - """ - - def __init__(self, num_timesteps: int): - """ - Args: - num_timesteps: The number of timesteps in the simulation. - Raises: - ValueError: If `steps` is not a positive integer. - """ - self.num_timesteps = num_timesteps - - def evolve(self, evolution_problem: TimeEvolutionProblem) -> TimeEvolutionResult: - r"""Perform real time evolution :math:`\exp(-i t H)|\Psi\rangle`. - - Evolves an initial state :math:`|\Psi\rangle` for a time :math:`t` - under a Hamiltonian :math:`H`, as provided in the ``evolution_problem``. - - Args: - evolution_problem: The definition of the evolution problem. - - Returns: - Evolution result which includes an evolved quantum state. - """ - return _evolve(evolution_problem, self.num_timesteps, real_time=True) diff --git a/qiskit/algorithms/time_evolvers/imaginary_time_evolver.py b/qiskit/algorithms/time_evolvers/imaginary_time_evolver.py deleted file mode 100644 index e62d02e5ab9c..000000000000 --- a/qiskit/algorithms/time_evolvers/imaginary_time_evolver.py +++ /dev/null @@ -1,37 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Interface for Quantum Imaginary Time Evolution.""" - -from abc import ABC, abstractmethod - -from .time_evolution_problem import TimeEvolutionProblem -from .time_evolution_result import TimeEvolutionResult - - -class ImaginaryTimeEvolver(ABC): - """Interface for Quantum Imaginary Time Evolution.""" - - @abstractmethod - def evolve(self, evolution_problem: TimeEvolutionProblem) -> TimeEvolutionResult: - r"""Perform imaginary time evolution :math:`\exp(-\tau H)|\Psi\rangle`. - - Evolves an initial state :math:`|\Psi\rangle` for an imaginary time :math:`\tau` - under a Hamiltonian :math:`H`, as provided in the ``evolution_problem``. - - Args: - evolution_problem: The definition of the evolution problem. - - Returns: - Evolution result which includes an evolved quantum state. - """ - raise NotImplementedError() diff --git a/qiskit/algorithms/time_evolvers/pvqd/__init__.py b/qiskit/algorithms/time_evolvers/pvqd/__init__.py deleted file mode 100644 index 9377ce631b4e..000000000000 --- a/qiskit/algorithms/time_evolvers/pvqd/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The projected Variational Quantum Dynamic (p-VQD) module.""" - -from .pvqd_result import PVQDResult -from .pvqd import PVQD - -__all__ = ["PVQD", "PVQDResult"] diff --git a/qiskit/algorithms/time_evolvers/pvqd/pvqd.py b/qiskit/algorithms/time_evolvers/pvqd/pvqd.py deleted file mode 100644 index bbd48df86651..000000000000 --- a/qiskit/algorithms/time_evolvers/pvqd/pvqd.py +++ /dev/null @@ -1,435 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The projected Variational Quantum Dynamics Algorithm.""" -from __future__ import annotations - -import logging -import warnings -from collections.abc import Callable - -import numpy as np - -from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit -from qiskit.circuit.library import PauliEvolutionGate -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseEstimator -from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.synthesis import EvolutionSynthesis, LieTrotter -from qiskit.utils import algorithm_globals - -from ...exceptions import AlgorithmError, QiskitError -from ...optimizers import Minimizer, Optimizer -from ...state_fidelities.base_state_fidelity import BaseStateFidelity -from ..real_time_evolver import RealTimeEvolver -from ..time_evolution_problem import TimeEvolutionProblem -from ..time_evolution_result import TimeEvolutionResult -from .pvqd_result import PVQDResult -from .utils import _get_observable_evaluator, _is_gradient_supported - -logger = logging.getLogger(__name__) - - -class PVQD(RealTimeEvolver): - """The projected Variational Quantum Dynamics (p-VQD) Algorithm. - - In each timestep, this algorithm computes the next state with a Trotter formula - (specified by the ``evolution`` argument) and projects the timestep onto a variational form - (``ansatz``). The projection is determined by maximizing the fidelity of the Trotter-evolved - state and the ansatz, using a classical optimization routine. See Ref. [1] for details. - - The following attributes can be set via the initializer but can also be read and - updated once the PVQD object has been constructed. - - Attributes: - - ansatz (QuantumCircuit): The parameterized circuit representing the time-evolved state. - initial_parameters (np.ndarray): The parameters of the ansatz at time 0. - optimizer (Optional[Union[Optimizer, Minimizer]]): The classical optimization routine - used to maximize the fidelity of the Trotter step and ansatz. - num_timesteps (Optional[int]): The number of timesteps to take. If None, it is automatically - selected to achieve a timestep of approximately 0.01. - evolution (Optional[EvolutionSynthesis]): The method to perform the Trotter step. - Defaults to first-order Lie-Trotter evolution. - use_parameter_shift (bool): If True, use the parameter shift rule for loss function - gradients (if the ansatz supports). - initial_guess (Optional[np.ndarray]): The starting point for the first classical optimization - run, at time 0. Defaults to random values in :math:`[-0.01, 0.01]`. - - Example: - - This snippet computes the real time evolution of a quantum Ising model on two - neighboring sites and keeps track of the magnetization. - - .. code-block:: python - - import numpy as np - - from qiskit.algorithms.state_fidelities import ComputeUncompute - from qiskit.algorithms.time_evolvers import TimeEvolutionProblem, PVQD - from qiskit.primitives import Estimator, Sampler - from qiskit.circuit.library import EfficientSU2 - from qiskit.quantum_info import SparsePauliOp, Pauli - from qiskit.algorithms.optimizers import L_BFGS_B - - sampler = Sampler() - fidelity = ComputeUncompute(sampler) - estimator = Estimator() - hamiltonian = 0.1 * SparsePauliOp(["ZZ", "IX", "XI"]) - observable = Pauli("ZZ") - ansatz = EfficientSU2(2, reps=1) - initial_parameters = np.zeros(ansatz.num_parameters) - - time = 1 - optimizer = L_BFGS_B() - - # setup the algorithm - pvqd = PVQD( - fidelity, - ansatz, - initial_parameters, - estimator, - num_timesteps=100, - optimizer=optimizer, - ) - - # specify the evolution problem - problem = TimeEvolutionProblem( - hamiltonian, time, aux_operators=[hamiltonian, observable] - ) - - # and evolve! - result = pvqd.evolve(problem) - - References: - - [1] Stefano Barison, Filippo Vicentini, and Giuseppe Carleo (2021), An efficient - quantum algorithm for the time evolution of parameterized circuits, - `Quantum 5, 512 `_. - """ - - def __init__( - self, - fidelity: BaseStateFidelity, - ansatz: QuantumCircuit, - initial_parameters: np.ndarray, - estimator: BaseEstimator | None = None, - optimizer: Optimizer | Minimizer | None = None, - num_timesteps: int | None = None, - evolution: EvolutionSynthesis | None = None, - use_parameter_shift: bool = True, - initial_guess: np.ndarray | None = None, - ) -> None: - """ - Args: - fidelity: A fidelity primitive used by the algorithm. - ansatz: A parameterized circuit preparing the variational ansatz to model the - time evolved quantum state. - initial_parameters: The initial parameters for the ansatz. Together with the ansatz, - these define the initial state of the time evolution. - estimator: An estimator primitive used for calculating expected values of auxiliary - operators (if provided via the problem). - optimizer: The classical optimizers used to minimize the overlap between - Trotterization and ansatz. Can be either a :class:`.Optimizer` or a callable - using the :class:`.Minimizer` protocol. This argument is optional since it is - not required for :meth:`get_loss`, but it has to be set before :meth:`evolve` - is called. - num_timesteps: The number of time steps. If ``None`` it will be set such that the - timestep is close to 0.01. - evolution: The evolution synthesis to use for the construction of the Trotter step. - Defaults to first-order Lie-Trotter decomposition, see also - :mod:`~qiskit.synthesis.evolution` for different options. - use_parameter_shift: If True, use the parameter shift rule to compute gradients. - If False, the optimizer will not be passed a gradient callable. In that case, - Qiskit optimizers will use a finite difference rule to approximate the gradients. - initial_guess: The initial guess for the first VQE optimization. Afterwards the - previous iteration result is used as initial guess. If None, this is set to - a random vector with elements in the interval :math:`[-0.01, 0.01]`. - """ - super().__init__() - if evolution is None: - evolution = LieTrotter() - - self.ansatz = ansatz - self.initial_parameters = initial_parameters - self.num_timesteps = num_timesteps - self.optimizer = optimizer - self.initial_guess = initial_guess - self.estimator = estimator - self.fidelity_primitive = fidelity - self.evolution = evolution - self.use_parameter_shift = use_parameter_shift - - def step( - self, - hamiltonian: BaseOperator | PauliSumOp, - ansatz: QuantumCircuit, - theta: np.ndarray, - dt: float, - initial_guess: np.ndarray, - ) -> tuple[np.ndarray, float]: - """Perform a single time step. - - Args: - hamiltonian: The Hamiltonian under which to evolve. - ansatz: The parameterized quantum circuit which attempts to approximate the - time-evolved state. - theta: The current parameters. - dt: The time step. - initial_guess: The initial guess for the classical optimization of the - fidelity between the next variational state and the Trotter-evolved last state. - If None, this is set to a random vector with elements in the interval - :math:`[-0.01, 0.01]`. - - Returns: - A tuple consisting of the next parameters and the fidelity of the optimization. - """ - self._validate_setup() - - loss, gradient = self.get_loss(hamiltonian, ansatz, dt, theta) - - if initial_guess is None: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - initial_guess = algorithm_globals.random.random(self.initial_parameters.size) * 0.01 - - if isinstance(self.optimizer, Optimizer): - optimizer_result = self.optimizer.minimize(loss, initial_guess, gradient) - else: - optimizer_result = self.optimizer(loss, initial_guess, gradient) - - # clip the fidelity to [0, 1] - fidelity = np.clip(1 - optimizer_result.fun, 0, 1) - - return theta + optimizer_result.x, fidelity - - def get_loss( - self, - hamiltonian: BaseOperator | PauliSumOp, - ansatz: QuantumCircuit, - dt: float, - current_parameters: np.ndarray, - ) -> tuple[Callable[[np.ndarray], float], Callable[[np.ndarray], np.ndarray]] | None: - """Get a function to evaluate the infidelity between Trotter step and ansatz. - - Args: - hamiltonian: The Hamiltonian under which to evolve. - ansatz: The parameterized quantum circuit which attempts to approximate the - time-evolved state. - dt: The time step. - current_parameters: The current parameters. - - Returns: - A callable to evaluate the infidelity and, if gradients are supported and required, - a second callable to evaluate the gradient of the infidelity. - """ - self._validate_setup(skip={"optimizer"}) - - # use Trotterization to evolve the current state - trotterized = ansatz.assign_parameters(current_parameters) - - evolution_gate = PauliEvolutionGate(hamiltonian, time=dt, synthesis=self.evolution) - - trotterized.append(evolution_gate, ansatz.qubits) - - # define the overlap of the Trotterized state and the ansatz - x = ParameterVector("w", ansatz.num_parameters) - shifted = ansatz.assign_parameters(current_parameters + x) - - def evaluate_loss(displacement: np.ndarray | list[np.ndarray]) -> float | np.ndarray: - """Evaluate the overlap of the ansatz with the Trotterized evolution. - - Args: - displacement: The parameters for the ansatz. - - Returns: - The fidelity of the ansatz with parameters ``theta`` and the Trotterized evolution. - - Raises: - AlgorithmError: If a primitive job fails. - """ - if isinstance(displacement, list): - displacement = np.asarray(displacement) - value_dict = {x_i: displacement[:, i].tolist() for i, x_i in enumerate(x)} - else: - value_dict = dict(zip(x, displacement)) - - param_dicts = self._transpose_param_dicts(value_dict) - num_of_param_sets = len(param_dicts) - states1 = [trotterized] * num_of_param_sets - states2 = [shifted] * num_of_param_sets - param_dicts2 = [list(param_dict.values()) for param_dict in param_dicts] - # the first state does not have free parameters so values_1 will be None by default - try: - job = self.fidelity_primitive.run(states1, states2, values_2=param_dicts2) - fidelities = np.array(job.result().fidelities) - except Exception as exc: - raise AlgorithmError("The primitive job failed!") from exc - - if len(fidelities) == 1: - fidelities = fidelities[0] - - # in principle, we could add different loss functions here, but we're currently - # not aware of a use-case for a different one than in the paper - return 1 - fidelities - - if _is_gradient_supported(ansatz) and self.use_parameter_shift: - - def evaluate_gradient(displacement: np.ndarray) -> np.ndarray: - """Evaluate the gradient with the parameter-shift rule. - - This is hardcoded here since the gradient framework does not support computing - gradients for overlaps. - - Args: - displacement: The parameters for the ansatz. - - Returns: - The gradient. - """ - # construct lists where each element is shifted by plus (or minus) pi/2 - dim = displacement.size - plus_shifts = (displacement + np.pi / 2 * np.identity(dim)).tolist() - minus_shifts = (displacement - np.pi / 2 * np.identity(dim)).tolist() - - evaluated = evaluate_loss(plus_shifts + minus_shifts) - - gradient = (evaluated[:dim] - evaluated[dim:]) / 2 - - return gradient - - else: - evaluate_gradient = None - - return evaluate_loss, evaluate_gradient - - def _transpose_param_dicts(self, params: dict) -> list[dict[Parameter, float]]: - p_0 = list(params.values())[0] - if isinstance(p_0, (list, np.ndarray)): - num_parameterizations = len(p_0) - param_bindings = [ - {param: value_list[i] for param, value_list in params.items()} # type: ignore - for i in range(num_parameterizations) - ] - else: - param_bindings = [params] - - return param_bindings - - def evolve(self, evolution_problem: TimeEvolutionProblem) -> TimeEvolutionResult: - r"""Perform real time evolution :math:`\exp(-i t H)|\Psi\rangle`. - - Evolves an initial state :math:`|\Psi\rangle` for a time :math:`t` - under a Hamiltonian :math:`H`, as provided in the ``evolution_problem``. - - Args: - evolution_problem: The evolution problem containing the hamiltonian, total evolution - time and observables to evaluate. - - Returns: - A result object containing the evolution information and evaluated observables. - - Raises: - ValueError: If ``aux_operators`` provided in the time evolution problem but no estimator - provided to the algorithm. - NotImplementedError: If the evolution problem contains an initial state. - """ - self._validate_setup() - - time = evolution_problem.time - observables = evolution_problem.aux_operators - hamiltonian = evolution_problem.hamiltonian - - # determine the number of timesteps and set the timestep - num_timesteps = ( - int(np.ceil(time / 0.01)) if self.num_timesteps is None else self.num_timesteps - ) - timestep = time / num_timesteps - - if evolution_problem.initial_state is not None: - raise NotImplementedError( - "Setting an initial state for the evolution is not yet supported for PVQD." - ) - - # get the function to evaluate the observables for a given set of ansatz parameters - if observables is not None: - if self.estimator is None: - raise ValueError( - "The evolution problem contained aux_operators but no estimator was provided. " - ) - evaluate_observables = _get_observable_evaluator( - self.ansatz, observables, self.estimator - ) - observable_values = [evaluate_observables(self.initial_parameters)] - - fidelities = [1.0] - parameters = [self.initial_parameters] - times = np.linspace(0, time, num_timesteps + 1).tolist() # +1 to include initial time 0 - - initial_guess = self.initial_guess - - for _ in range(num_timesteps): - # perform VQE to find the next parameters - next_parameters, fidelity = self.step( - hamiltonian, self.ansatz, parameters[-1], timestep, initial_guess - ) - - # set initial guess to last parameter update - initial_guess = next_parameters - parameters[-1] - - parameters.append(next_parameters) - fidelities.append(fidelity) - if observables is not None: - observable_values.append(evaluate_observables(next_parameters)) - - evolved_state = self.ansatz.assign_parameters(parameters[-1]) - - result = PVQDResult( - evolved_state=evolved_state, - times=times, - parameters=parameters, - fidelities=fidelities, - estimated_error=1 - np.prod(fidelities), - ) - if observables is not None: - result.observables = observable_values - result.aux_ops_evaluated = observable_values[-1] - - return result - - def _validate_setup(self, skip=None): - """Validate the current setup and raise an error if something misses to run.""" - - if skip is None: - skip = {} - - required_attributes = {"optimizer"}.difference(skip) - - for attr in required_attributes: - if getattr(self, attr, None) is None: - raise ValueError(f"The {attr} cannot be None.") - - if self.num_timesteps is not None and self.num_timesteps <= 0: - raise ValueError( - f"The number of timesteps must be positive but is {self.num_timesteps}." - ) - - if self.ansatz.num_parameters == 0: - raise QiskitError( - "The ansatz cannot have 0 parameters, otherwise it cannot be trained." - ) - - if len(self.initial_parameters) != self.ansatz.num_parameters: - raise QiskitError( - f"Mismatching number of parameters in the ansatz ({self.ansatz.num_parameters}) " - f"and the initial parameters ({len(self.initial_parameters)})." - ) diff --git a/qiskit/algorithms/time_evolvers/pvqd/pvqd_result.py b/qiskit/algorithms/time_evolvers/pvqd/pvqd_result.py deleted file mode 100644 index 65c2a8b18604..000000000000 --- a/qiskit/algorithms/time_evolvers/pvqd/pvqd_result.py +++ /dev/null @@ -1,54 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Result object for p-VQD.""" -from __future__ import annotations - -from collections.abc import Sequence - -import numpy as np -from qiskit.circuit import QuantumCircuit -from ..time_evolution_result import TimeEvolutionResult - - -class PVQDResult(TimeEvolutionResult): - """The result object for the p-VQD algorithm.""" - - def __init__( - self, - evolved_state: QuantumCircuit, - aux_ops_evaluated: list[tuple[complex, complex]] | None = None, - times: list[float] | None = None, - parameters: list[np.ndarray] | None = None, - fidelities: Sequence[float] | None = None, - estimated_error: float | None = None, - observables: list[list[float]] | None = None, - ): - """ - Args: - evolved_state: An evolved quantum state. - aux_ops_evaluated: Optional list of observables for which expected values on an evolved - state are calculated. These values are in fact tuples formatted as (mean, standard - deviation). - times: The times evaluated during the time integration. - parameters: The parameter values at each evaluation time. - fidelities: The fidelity of the Trotter step and variational update at each iteration. - estimated_error: The overall estimated error evaluated as one minus the - product of all fidelities. - observables: The value of the observables evaluated at each iteration. - """ - super().__init__(evolved_state, aux_ops_evaluated) - self.times = times - self.parameters = parameters - self.fidelities = fidelities - self.estimated_error = estimated_error - self.observables = observables diff --git a/qiskit/algorithms/time_evolvers/pvqd/utils.py b/qiskit/algorithms/time_evolvers/pvqd/utils.py deleted file mode 100644 index 9b3f330dd350..000000000000 --- a/qiskit/algorithms/time_evolvers/pvqd/utils.py +++ /dev/null @@ -1,109 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - - -"""Utilities for p-VQD.""" -from __future__ import annotations -import logging -from collections.abc import Callable - -import numpy as np - -from qiskit.circuit import QuantumCircuit, Parameter, ParameterExpression -from qiskit.compiler import transpile -from qiskit.exceptions import QiskitError -from qiskit.opflow.gradients.circuit_gradients import ParamShift -from qiskit.primitives import BaseEstimator -from qiskit.quantum_info.operators.base_operator import BaseOperator -from ...exceptions import AlgorithmError - -logger = logging.getLogger(__name__) - - -def _is_gradient_supported(ansatz: QuantumCircuit) -> bool: - """Check whether we can apply a simple parameter shift rule to obtain gradients.""" - - # check whether the circuit can be unrolled to supported gates - try: - unrolled = transpile(ansatz, basis_gates=ParamShift.SUPPORTED_GATES, optimization_level=0) - except QiskitError: - # failed to map to supported basis - logger.log( - logging.INFO, - "No gradient support: Failed to unroll to gates supported by parameter-shift.", - ) - return False - - # check whether all parameters are unique and we do not need to apply the chain rule - # (since it's not implemented yet) - total_num_parameters = 0 - for circuit_instruction in unrolled.data: - for param in circuit_instruction.operation.params: - if isinstance(param, ParameterExpression): - if isinstance(param, Parameter): - total_num_parameters += 1 - else: - logger.log( - logging.INFO, - "No gradient support: Circuit is only allowed to have plain parameters, " - "as the chain rule is not yet implemented.", - ) - return False - - if total_num_parameters != ansatz.num_parameters: - logger.log( - logging.INFO, - "No gradient support: Circuit is only allowed to have unique parameters, " - "as the product rule is not yet implemented.", - ) - return False - - return True - - -def _get_observable_evaluator( - ansatz: QuantumCircuit, - observables: BaseOperator | list[BaseOperator], - estimator: BaseEstimator, -) -> Callable[[np.ndarray], float | list[float]]: - """Get a callable to evaluate a (list of) observable(s) for given circuit parameters.""" - - def evaluate_observables(theta: np.ndarray) -> float | list[float]: - """Evaluate the observables for the ansatz parameters ``theta``. - - Args: - theta: The ansatz parameters. - - Returns: - The observables evaluated at the ansatz parameters. - - Raises: - AlgorithmError: If a primitive job fails. - """ - if isinstance(observables, list): - num_observables = len(observables) - obs = observables - else: - num_observables = 1 - obs = [observables] - states = [ansatz] * num_observables - parameter_values = [theta] * num_observables - - try: - estimator_job = estimator.run(states, obs, parameter_values=parameter_values) - results = estimator_job.result().values - except Exception as exc: - raise AlgorithmError("The primitive job failed!") from exc - - return results - - return evaluate_observables diff --git a/qiskit/algorithms/time_evolvers/real_time_evolver.py b/qiskit/algorithms/time_evolvers/real_time_evolver.py deleted file mode 100644 index 585da953755b..000000000000 --- a/qiskit/algorithms/time_evolvers/real_time_evolver.py +++ /dev/null @@ -1,37 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Interface for Quantum Real Time Evolution.""" - -from abc import ABC, abstractmethod - -from .time_evolution_problem import TimeEvolutionProblem -from .time_evolution_result import TimeEvolutionResult - - -class RealTimeEvolver(ABC): - """Interface for Quantum Real Time Evolution.""" - - @abstractmethod - def evolve(self, evolution_problem: TimeEvolutionProblem) -> TimeEvolutionResult: - r"""Perform real time evolution :math:`\exp(-i t H)|\Psi\rangle`. - - Evolves an initial state :math:`|\Psi\rangle` for a time :math:`t` - under a Hamiltonian :math:`H`, as provided in the ``evolution_problem``. - - Args: - evolution_problem: The definition of the evolution problem. - - Returns: - Evolution result which includes an evolved quantum state. - """ - raise NotImplementedError() diff --git a/qiskit/algorithms/time_evolvers/time_evolution_problem.py b/qiskit/algorithms/time_evolvers/time_evolution_problem.py deleted file mode 100644 index 87159558baf5..000000000000 --- a/qiskit/algorithms/time_evolvers/time_evolution_problem.py +++ /dev/null @@ -1,114 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Time evolution problem class.""" -from __future__ import annotations - -from collections.abc import Mapping - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter, ParameterExpression -from qiskit.opflow import PauliSumOp -from ..list_or_dict import ListOrDict -from ...quantum_info import Statevector -from ...quantum_info.operators.base_operator import BaseOperator - - -class TimeEvolutionProblem: - """Time evolution problem class. - - This class is the input to time evolution algorithms and must contain information on the total - evolution time, a quantum state to be evolved and under which Hamiltonian the state is evolved. - - Attributes: - hamiltonian (BaseOperator | PauliSumOp): The Hamiltonian under which to evolve the system. - initial_state (QuantumCircuit | Statevector | None): The quantum state to be evolved for - methods like Trotterization. For variational time evolutions, where the evolution - happens in an ansatz, this argument is not required. - aux_operators (ListOrDict[BaseOperator | PauliSumOp] | None): Optional list of auxiliary - operators to be evaluated with the evolved ``initial_state`` and their expectation - values returned. - truncation_threshold (float): Defines a threshold under which values can be assumed to be 0. - Used when ``aux_operators`` is provided. - t_param (Parameter | None): Time parameter in case of a time-dependent Hamiltonian. This - free parameter must be within the ``hamiltonian``. - param_value_map (dict[Parameter, complex] | None): Maps free parameters in the problem to - values. Depending on the algorithm, it might refer to e.g. a Hamiltonian or an initial - state. - """ - - def __init__( - self, - hamiltonian: BaseOperator | PauliSumOp, - time: float, - initial_state: QuantumCircuit | Statevector | None = None, - aux_operators: ListOrDict[BaseOperator | PauliSumOp] | None = None, - truncation_threshold: float = 1e-12, - t_param: Parameter | None = None, - param_value_map: Mapping[Parameter, complex] | None = None, - ): - """ - Args: - hamiltonian: The Hamiltonian under which to evolve the system. - time: Total time of evolution. - initial_state: The quantum state to be evolved for methods like Trotterization. - For variational time evolutions, where the evolution happens in an ansatz, - this argument is not required. - aux_operators: Optional list of auxiliary operators to be evaluated with the - evolved ``initial_state`` and their expectation values returned. - truncation_threshold: Defines a threshold under which values can be assumed to be 0. - Used when ``aux_operators`` is provided. - t_param: Time parameter in case of a time-dependent Hamiltonian. This - free parameter must be within the ``hamiltonian``. - param_value_map: Maps free parameters in the problem to values. Depending on the - algorithm, it might refer to e.g. a Hamiltonian or an initial state. - - Raises: - ValueError: If non-positive time of evolution is provided. - """ - - self.t_param = t_param - self.param_value_map = param_value_map - self.hamiltonian = hamiltonian - self.time = time - if isinstance(initial_state, Statevector): - circuit = QuantumCircuit(initial_state.num_qubits) - circuit.prepare_state(initial_state.data) - initial_state = circuit - self.initial_state: QuantumCircuit | None = initial_state - self.aux_operators = aux_operators - self.truncation_threshold = truncation_threshold - - @property - def time(self) -> float: - """Returns time.""" - return self._time - - @time.setter - def time(self, time: float) -> None: - """ - Sets time and validates it. - """ - self._time = time - - def validate_params(self) -> None: - """ - Checks if all parameters present in the Hamiltonian are also present in the dictionary - that maps them to values. - - Raises: - ValueError: If Hamiltonian parameters cannot be bound with data provided. - """ - if isinstance(self.hamiltonian, PauliSumOp) and isinstance( - self.hamiltonian.coeff, ParameterExpression - ): - raise ValueError("A global parametrized coefficient for PauliSumOp is not allowed.") diff --git a/qiskit/algorithms/time_evolvers/time_evolution_result.py b/qiskit/algorithms/time_evolvers/time_evolution_result.py deleted file mode 100644 index 8741367f681f..000000000000 --- a/qiskit/algorithms/time_evolvers/time_evolution_result.py +++ /dev/null @@ -1,60 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Class for holding time evolution result.""" -from __future__ import annotations -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.quantum_info import Statevector -from qiskit.algorithms.list_or_dict import ListOrDict -from ..algorithm_result import AlgorithmResult - - -class TimeEvolutionResult(AlgorithmResult): - """ - Class for holding time evolution result. - - Attributes: - evolved_state (QuantumCircuit|Statevector): An evolved quantum state. - aux_ops_evaluated (ListOrDict[tuple[complex, complex]] | None): Optional list of - observables for which expected values on an evolved state are calculated. These values - are in fact tuples formatted as (mean, standard deviation). - observables (ListOrDict[tuple[np.ndarray, np.ndarray]] | None): Optional list of - observables for which expected on an evolved state are calculated at each timestep. - These values are in fact lists of tuples formatted as (mean, standard deviation). - times (np.array | None): Optional list of times at which each observable has been evaluated. - """ - - def __init__( - self, - evolved_state: QuantumCircuit | Statevector, - aux_ops_evaluated: ListOrDict[tuple[complex, complex]] | None = None, - observables: ListOrDict[tuple[np.ndarray, np.ndarray]] | None = None, - times: np.ndarray | None = None, - ): - """ - Args: - evolved_state: An evolved quantum state. - aux_ops_evaluated: Optional list of observables for which expected values on an evolved - state are calculated. These values are in fact tuples formatted as (mean, standard - deviation). - observables: Optional list of observables for which expected values are calculated for - each timestep. These values are in fact tuples formatted as (mean array, standard - deviation array). - times: Optional list of times at which each observable has been evaluated. - """ - - self.evolved_state = evolved_state - self.aux_ops_evaluated = aux_ops_evaluated - self.observables = observables - self.times = times diff --git a/qiskit/algorithms/time_evolvers/trotterization/__init__.py b/qiskit/algorithms/time_evolvers/trotterization/__init__.py deleted file mode 100644 index c5e7e128728d..000000000000 --- a/qiskit/algorithms/time_evolvers/trotterization/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""This package contains Trotterization-based Quantum Real Time Evolution algorithm. -It is compliant with the new Quantum Time Evolution Framework and makes use of -:class:`qiskit.synthesis.evolution.ProductFormula` and -:class:`~qiskit.circuit.library.PauliEvolutionGate` implementations. - -Trotterization-based Quantum Real Time Evolution ------------------------------------------------- - -.. autosummary:: - :toctree: ../stubs/ - :nosignatures: - - TrotterQRTE -""" - -from qiskit.algorithms.time_evolvers.trotterization.trotter_qrte import TrotterQRTE - -__all__ = ["TrotterQRTE"] diff --git a/qiskit/algorithms/time_evolvers/trotterization/trotter_qrte.py b/qiskit/algorithms/time_evolvers/trotterization/trotter_qrte.py deleted file mode 100644 index cb43e297aed2..000000000000 --- a/qiskit/algorithms/time_evolvers/trotterization/trotter_qrte.py +++ /dev/null @@ -1,246 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""An algorithm to implement a Trotterization real time-evolution.""" - -from __future__ import annotations - -from qiskit import QuantumCircuit -from qiskit.algorithms.time_evolvers.time_evolution_problem import TimeEvolutionProblem -from qiskit.algorithms.time_evolvers.time_evolution_result import TimeEvolutionResult -from qiskit.algorithms.time_evolvers.real_time_evolver import RealTimeEvolver -from qiskit.algorithms.observables_evaluator import estimate_observables -from qiskit.opflow import PauliSumOp -from qiskit.circuit.library import PauliEvolutionGate -from qiskit.circuit.parametertable import ParameterView -from qiskit.primitives import BaseEstimator -from qiskit.quantum_info import Pauli, SparsePauliOp -from qiskit.synthesis import ProductFormula, LieTrotter - - -class TrotterQRTE(RealTimeEvolver): - """Quantum Real Time Evolution using Trotterization. - Type of Trotterization is defined by a ``ProductFormula`` provided. - - Examples: - - .. code-block:: python - - from qiskit.opflow import PauliSumOp - from qiskit.quantum_info import Pauli, SparsePauliOp - from qiskit import QuantumCircuit - from qiskit.algorithms import TimeEvolutionProblem - from qiskit.algorithms.time_evolvers import TrotterQRTE - from qiskit.primitives import Estimator - - operator = PauliSumOp(SparsePauliOp([Pauli("X"), Pauli("Z")])) - initial_state = QuantumCircuit(1) - time = 1 - evolution_problem = TimeEvolutionProblem(operator, time, initial_state) - # LieTrotter with 1 rep - estimator = Estimator() - trotter_qrte = TrotterQRTE(estimator=estimator) - evolved_state = trotter_qrte.evolve(evolution_problem).evolved_state - """ - - def __init__( - self, - product_formula: ProductFormula | None = None, - estimator: BaseEstimator | None = None, - num_timesteps: int = 1, - ) -> None: - """ - Args: - product_formula: A Lie-Trotter-Suzuki product formula. If ``None`` provided, the - Lie-Trotter first order product formula with a single repetition is used. ``reps`` - should be 1 to obtain a number of time-steps equal to ``num_timesteps`` and an - evaluation of :attr:`.TimeEvolutionProblem.aux_operators` at every time-step. If ``reps`` - is larger than 1, the true number of time-steps will be ``num_timesteps * reps``. - num_timesteps: The number of time-steps the full evolution time is devided into - (repetitions of ``product_formula``) - estimator: An estimator primitive used for calculating expectation values of - ``TimeEvolutionProblem.aux_operators``. - """ - - self.product_formula = product_formula - self.num_timesteps = num_timesteps - self.estimator = estimator - - @property - def product_formula(self) -> ProductFormula: - """Returns a product formula.""" - return self._product_formula - - @product_formula.setter - def product_formula(self, product_formula: ProductFormula | None): - """Sets a product formula. If ``None`` provided, sets the Lie-Trotter first order product - formula with a single repetition.""" - if product_formula is None: - product_formula = LieTrotter() - self._product_formula = product_formula - - @property - def estimator(self) -> BaseEstimator | None: - """ - Returns an estimator. - """ - return self._estimator - - @estimator.setter - def estimator(self, estimator: BaseEstimator) -> None: - """ - Sets an estimator. - """ - self._estimator = estimator - - @property - def num_timesteps(self) -> int: - """Returns the number of timesteps.""" - return self._num_timesteps - - @num_timesteps.setter - def num_timesteps(self, num_timesteps: int) -> None: - """ - Sets the number of time-steps. - - Raises: - ValueError: If num_timesteps is not positive. - """ - if num_timesteps <= 0: - raise ValueError( - f"Number of time steps must be positive integer, {num_timesteps} provided" - ) - self._num_timesteps = num_timesteps - - @classmethod - def supports_aux_operators(cls) -> bool: - """ - Whether computing the expectation value of auxiliary operators is supported. - - Returns: - ``True`` if ``aux_operators`` expectations in the ``TimeEvolutionProblem`` can be - evaluated, ``False`` otherwise. - """ - return True - - def evolve(self, evolution_problem: TimeEvolutionProblem) -> TimeEvolutionResult: - """ - Evolves a quantum state for a given time using the Trotterization method - based on a product formula provided. The result is provided in the form of a quantum - circuit. If auxiliary operators are included in the ``evolution_problem``, they are - evaluated on the ``init_state`` and on the evolved state at every step (``num_timesteps`` - times) using an estimator primitive provided. - - Args: - evolution_problem: Instance defining evolution problem. For the included Hamiltonian, - ``Pauli`` or ``PauliSumOp`` are supported by TrotterQRTE. - - Returns: - Evolution result that includes an evolved state as a quantum circuit and, optionally, - auxiliary operators evaluated for a resulting state on an estimator primitive. - - Raises: - ValueError: If ``t_param`` is not set to ``None`` in the ``TimeEvolutionProblem`` - (feature not currently supported). - ValueError: If ``aux_operators`` provided in the time evolution problem but no estimator - provided to the algorithm. - ValueError: If the ``initial_state`` is not provided in the ``TimeEvolutionProblem``. - ValueError: If an unsupported Hamiltonian type is provided. - """ - evolution_problem.validate_params() - - if evolution_problem.aux_operators is not None and self.estimator is None: - raise ValueError( - "The time evolution problem contained ``aux_operators`` but no estimator was " - "provided. The algorithm continues without calculating these quantities. " - ) - - # ensure the hamiltonian is a sparse pauli op - hamiltonian = evolution_problem.hamiltonian - if not isinstance(hamiltonian, (Pauli, PauliSumOp, SparsePauliOp)): - raise ValueError( - f"TrotterQRTE only accepts Pauli | PauliSumOp | SparsePauliOp, {type(hamiltonian)} " - "provided." - ) - if isinstance(hamiltonian, PauliSumOp): - hamiltonian = hamiltonian.primitive * hamiltonian.coeff - elif isinstance(hamiltonian, Pauli): - hamiltonian = SparsePauliOp(hamiltonian) - - t_param = evolution_problem.t_param - free_parameters = hamiltonian.parameters - if t_param is not None and free_parameters != ParameterView([t_param]): - raise ValueError( - f"Hamiltonian time parameters ({free_parameters}) do not match " - f"evolution_problem.t_param ({t_param})." - ) - - # make sure PauliEvolutionGate does not implement more than one Trotter step - dt = evolution_problem.time / self.num_timesteps - - if evolution_problem.initial_state is not None: - initial_state = evolution_problem.initial_state - else: - raise ValueError("``initial_state`` must be provided in the ``TimeEvolutionProblem``.") - - evolved_state = QuantumCircuit(initial_state.num_qubits) - evolved_state.append(initial_state, evolved_state.qubits) - - if evolution_problem.aux_operators is not None: - observables = [] - observables.append( - estimate_observables( - self.estimator, - evolved_state, - evolution_problem.aux_operators, - None, - evolution_problem.truncation_threshold, - ) - ) - else: - observables = None - - if t_param is None: - # the evolution gate - single_step_evolution_gate = PauliEvolutionGate( - hamiltonian, dt, synthesis=self.product_formula - ) - - for n in range(self.num_timesteps): - # if hamiltonian is time-dependent, bind new time-value at every step to construct - # evolution for next step - if t_param is not None: - time_value = (n + 1) * dt - bound_hamiltonian = hamiltonian.assign_parameters([time_value]) - single_step_evolution_gate = PauliEvolutionGate( - bound_hamiltonian, - dt, - synthesis=self.product_formula, - ) - evolved_state.append(single_step_evolution_gate, evolved_state.qubits) - - if evolution_problem.aux_operators is not None: - observables.append( - estimate_observables( - self.estimator, - evolved_state, - evolution_problem.aux_operators, - None, - evolution_problem.truncation_threshold, - ) - ) - - evaluated_aux_ops = None - if evolution_problem.aux_operators is not None: - evaluated_aux_ops = observables[-1] - - return TimeEvolutionResult(evolved_state, evaluated_aux_ops, observables) diff --git a/qiskit/algorithms/time_evolvers/variational/__init__.py b/qiskit/algorithms/time_evolvers/variational/__init__.py deleted file mode 100644 index ae22bca5adea..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/__init__.py +++ /dev/null @@ -1,117 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -Variational Quantum Time Evolutions (:mod:`qiskit.algorithms.time_evolvers.variational`) -======================================================================================== - -Algorithms for performing Variational Quantum Time Evolution of quantum states, -which can be tailored to near-term devices. -:class:`~qiskit.algorithms.time_evolvers.variational.VarQTE` base class exposes an interface, compliant -with the Quantum Time Evolution Framework in Qiskit Terra, that is implemented by -:class:`~qiskit.algorithms.VarQRTE` and :class:`~qiskit.algorithms.VarQITE` classes for real and -imaginary time evolution respectively. The variational approach is taken according to a variational -principle chosen by a user. - -Example: - - .. code-block:: python - - import numpy as np - - from qiskit.algorithms import TimeEvolutionProblem, VarQITE - from qiskit.algorithms.time_evolvers.variational import ImaginaryMcLachlanPrinciple - from qiskit.circuit.library import EfficientSU2 - from qiskit.quantum_info import SparsePauliOp - - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - ansatz = EfficientSU2(observable.num_qubits, reps=1) - init_param_values = np.zeros(len(ansatz.parameters)) - for i in range(len(ansatz.parameters)): - init_param_values[i] = np.pi / 2 - var_principle = ImaginaryMcLachlanPrinciple() - time = 1 - evolution_problem = TimeEvolutionProblem(observable, time) - var_qite = VarQITE(ansatz, var_principle, init_param_values) - evolution_result = var_qite.evolve(evolution_problem) - -.. currentmodule:: qiskit.algorithms.time_evolvers.variational - -Variational Principles ----------------------- - -With variational principles we can project time evolution of a quantum state -onto the parameters of a model, in our case a variational quantum circuit. - -They can be divided into two categories: Variational Quantum _Real_ Time Evolution, which evolves -the variational ansatz under the standard Schroediger equation and -Variational Quantum _Imaginary_ Time Evolution, which evolves under the normalized -Wick-rotated Schroedinger equation. - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - VariationalPrinciple - RealVariationalPrinciple - ImaginaryVariationalPrinciple - RealMcLachlanPrinciple - ImaginaryMcLachlanPrinciple - -ODE solvers ------------ -ODE solvers that implement the SciPy ODE Solver interface. The Forward Euler Solver is -a preferred choice in the presence of noise. One might also use solvers provided by SciPy directly, -e.g. RK45. - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - ForwardEulerSolver - -""" -from .solvers.ode.forward_euler_solver import ForwardEulerSolver -from .var_qrte import VarQRTE -from .var_qite import VarQITE - -from .var_qte import VarQTE -from .var_qte_result import VarQTEResult -from .variational_principles import ( - VariationalPrinciple, - RealVariationalPrinciple, - ImaginaryVariationalPrinciple, - ImaginaryMcLachlanPrinciple, - RealMcLachlanPrinciple, -) - -__all__ = [ - "ForwardEulerSolver", - "VarQTE", - "VarQTEResult", - "VariationalPrinciple", - "RealVariationalPrinciple", - "ImaginaryVariationalPrinciple", - "RealMcLachlanPrinciple", - "ImaginaryMcLachlanPrinciple", - "VarQITE", - "VarQRTE", -] diff --git a/qiskit/algorithms/time_evolvers/variational/solvers/__init__.py b/qiskit/algorithms/time_evolvers/variational/solvers/__init__.py deleted file mode 100644 index b4537b41e839..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/solvers/__init__.py +++ /dev/null @@ -1,48 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Solvers (:mod:`qiskit.algorithms.time_evolvers.variational.solvers`) -==================================================================== - -This package contains the necessary classes to solve systems of equations arising in the -Variational Quantum Time Evolution. They include ordinary differential equations (ODE) which -describe ansatz parameter propagation and systems of linear equations. - - -Systems of Linear Equations Solver ----------------------------------- - -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - VarQTELinearSolver - - -ODE Solver ----------- -.. autosummary:: - :toctree: ../stubs/ - :template: autosummary/class_no_inherited_members.rst - - VarQTEOdeSolver -""" - -from qiskit.algorithms.time_evolvers.variational.solvers.ode.var_qte_ode_solver import ( - VarQTEOdeSolver, -) -from qiskit.algorithms.time_evolvers.variational.solvers.var_qte_linear_solver import ( - VarQTELinearSolver, -) - -__all__ = ["VarQTELinearSolver", "VarQTEOdeSolver"] diff --git a/qiskit/algorithms/time_evolvers/variational/solvers/ode/__init__.py b/qiskit/algorithms/time_evolvers/variational/solvers/ode/__init__.py deleted file mode 100644 index 06684cb2d012..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/solvers/ode/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""ODE Solvers""" diff --git a/qiskit/algorithms/time_evolvers/variational/solvers/ode/abstract_ode_function.py b/qiskit/algorithms/time_evolvers/variational/solvers/ode/abstract_ode_function.py deleted file mode 100644 index b94ded552a81..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/solvers/ode/abstract_ode_function.py +++ /dev/null @@ -1,51 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Abstract class for generating ODE functions.""" -from __future__ import annotations - -from abc import ABC, abstractmethod -from collections.abc import Mapping, Iterable - -from qiskit.circuit import Parameter - -from ..var_qte_linear_solver import VarQTELinearSolver - - -class AbstractOdeFunction(ABC): - """Abstract class for generating ODE functions.""" - - def __init__( - self, - varqte_linear_solver: VarQTELinearSolver, - param_dict: Mapping[Parameter, float], - t_param: Parameter | None = None, - ) -> None: - - self._varqte_linear_solver = varqte_linear_solver - self._param_dict = param_dict - self._t_param = t_param - - @abstractmethod - def var_qte_ode_function(self, time: float, parameter_values: Iterable) -> Iterable: - """ - Evaluates an ODE function for a given time and parameter values. It is used by an ODE - solver. - - Args: - time: Current time of evolution. - parameter_values: Current values of parameters. - - Returns: - ODE gradient arising from solving a system of linear equations. - """ - pass diff --git a/qiskit/algorithms/time_evolvers/variational/solvers/ode/forward_euler_solver.py b/qiskit/algorithms/time_evolvers/variational/solvers/ode/forward_euler_solver.py deleted file mode 100644 index d48ee5b6c4e1..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/solvers/ode/forward_euler_solver.py +++ /dev/null @@ -1,72 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""Forward Euler ODE solver.""" -from collections.abc import Callable, Sequence - -import numpy as np -from scipy.integrate import OdeSolver -from scipy.integrate._ivp.base import ConstantDenseOutput - - -class ForwardEulerSolver(OdeSolver): - """Forward Euler ODE solver.""" - - def __init__( - self, - function: Callable, - t0: float, - y0: Sequence, - t_bound: float, - vectorized: bool = False, - support_complex: bool = False, - num_t_steps: int = 15, - ): - """ - Forward Euler ODE solver that implements an interface from SciPy. - - Args: - function: Right-hand side of the system. The calling signature is ``fun(t, y)``. Here - ``t`` is a scalar, and there are two options for the ndarray ``y``: - It can either have shape (n,); then ``fun`` must return array_like with - shape (n,). Alternatively it can have shape (n, k); then ``fun`` - must return an array_like with shape (n, k), i.e., each column - corresponds to a single column in ``y``. The choice between the two - options is determined by `vectorized` argument (see below). The - vectorized implementation allows a faster approximation of the Jacobian - by finite differences (required for this solver). - t0: Initial time. - y0: Initial state. - t_bound: Boundary time - the integration won't continue beyond it. It also determines - the direction of the integration. - vectorized: Whether ``fun`` is implemented in a vectorized fashion. Default is False. - support_complex: Whether integration in a complex domain should be supported. - Generally determined by a derived solver class capabilities. Default is False. - num_t_steps: Number of time steps for the forward Euler method. - """ - self._y_old = None - self._step_length = (t_bound - t0) / num_t_steps - super().__init__(function, t0, y0, t_bound, vectorized, support_complex) - - def _step_impl(self): - """ - Takes an Euler step. - """ - try: - self._y_old = self.y - self.y = list(np.add(self.y, self._step_length * self.fun(self.t, self.y))) - self.t += self._step_length - return True, None - except Exception as ex: # pylint: disable=broad-except - return False, f"Unknown ODE solver error: {str(ex)}." - - def _dense_output_impl(self): - return ConstantDenseOutput(self.t_old, self.t, self._y_old) diff --git a/qiskit/algorithms/time_evolvers/variational/solvers/ode/ode_function.py b/qiskit/algorithms/time_evolvers/variational/solvers/ode/ode_function.py deleted file mode 100644 index a7d8453c29b8..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/solvers/ode/ode_function.py +++ /dev/null @@ -1,41 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Class for generating ODE functions based on ODE gradients.""" -from collections.abc import Iterable - -from .abstract_ode_function import AbstractOdeFunction - - -class OdeFunction(AbstractOdeFunction): - """Class for generating ODE functions based on ODE gradients.""" - - def var_qte_ode_function(self, time: float, parameter_values: Iterable) -> Iterable: - """ - Evaluates an ODE function for a given time and parameter values. It is used by an ODE - solver. - - Args: - time: Current time of evolution. - parameter_values: Current values of parameters. - - Returns: - ODE gradient arising from solving a system of linear equations. - """ - current_param_dict = dict(zip(self._param_dict.keys(), parameter_values)) - - ode_grad_res, _, _ = self._varqte_linear_solver.solve_lse( - current_param_dict, - time, - ) - - return ode_grad_res diff --git a/qiskit/algorithms/time_evolvers/variational/solvers/ode/ode_function_factory.py b/qiskit/algorithms/time_evolvers/variational/solvers/ode/ode_function_factory.py deleted file mode 100644 index 0d094c4b7950..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/solvers/ode/ode_function_factory.py +++ /dev/null @@ -1,72 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Abstract class for generating ODE functions.""" -from __future__ import annotations - -from abc import ABC -from collections.abc import Mapping -from enum import Enum - -from qiskit.circuit import Parameter - -from .abstract_ode_function import AbstractOdeFunction -from .ode_function import OdeFunction - -from ..var_qte_linear_solver import VarQTELinearSolver - - -class OdeFunctionType(Enum): - """Types of ODE functions for VatQTE algorithms.""" - - # Other types may be supported in the future - STANDARD_ODE = "STANDARD_ODE" - - -class OdeFunctionFactory(ABC): - """Factory for building ODE functions.""" - - def __init__(self, ode_function_type: OdeFunctionType = OdeFunctionType.STANDARD_ODE) -> None: - """ - Args: - ode_function_type: An Enum that defines a type of an ODE function to be built. If - not provided, a default ``STANDARD_ODE`` is used. - """ - self._ode_function_type = ode_function_type - - def _build( - self, - varqte_linear_solver: VarQTELinearSolver, - param_dict: Mapping[Parameter, float], - t_param: Parameter | None = None, - ) -> AbstractOdeFunction: - """ - Initializes an ODE function specified in the class. - - Args: - varqte_linear_solver: Solver of LSE for the VarQTE algorithm. - param_dict: Dictionary which relates parameter values to the parameters in the ansatz. - t_param: Time parameter in case of a time-dependent Hamiltonian. - - Returns: - An ODE function. - - Raises: - ValueError: If unsupported ODE function provided. - - """ - if self._ode_function_type == OdeFunctionType.STANDARD_ODE: - return OdeFunction(varqte_linear_solver, param_dict, t_param) - raise ValueError( - f"Unsupported ODE function provided: {self._ode_function_type}." - f" Only {[tp.value for tp in OdeFunctionType]} are supported." - ) diff --git a/qiskit/algorithms/time_evolvers/variational/solvers/ode/var_qte_ode_solver.py b/qiskit/algorithms/time_evolvers/variational/solvers/ode/var_qte_ode_solver.py deleted file mode 100644 index aad1d96155ce..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/solvers/ode/var_qte_ode_solver.py +++ /dev/null @@ -1,89 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Class for solving ODEs for Quantum Time Evolution.""" -from __future__ import annotations - -from collections.abc import Sequence -from functools import partial -from typing import Type - -import numpy as np -from scipy.integrate import OdeSolver, solve_ivp - -from .abstract_ode_function import AbstractOdeFunction -from .forward_euler_solver import ForwardEulerSolver - - -class VarQTEOdeSolver: - """Class for solving ODEs for Quantum Time Evolution.""" - - def __init__( - self, - init_params: Sequence[float], - ode_function: AbstractOdeFunction, - ode_solver: Type[OdeSolver] | str = ForwardEulerSolver, - num_timesteps: int | None = None, - ) -> None: - """ - Initialize ODE Solver. - - Args: - init_params: Set of initial parameters for time 0. - ode_function: Generates the ODE function. - ode_solver: ODE solver callable that implements a SciPy ``OdeSolver`` interface or a - string indicating a valid method offered by SciPy. - num_timesteps: The number of timesteps to take. If None, it is - automatically selected to achieve a timestep of approximately 0.01. Only - relevant in case of the ``ForwardEulerSolver``. - """ - self._init_params = init_params - self._ode_function = ode_function.var_qte_ode_function - self._ode_solver = ode_solver - self._num_timesteps = num_timesteps - - def run( - self, evolution_time: float - ) -> tuple[Sequence[float], Sequence[Sequence[float]], Sequence[float]]: - """ - Finds numerical solution with ODE Solver. - - Args: - evolution_time: Evolution time. - - Returns: - List of parameters found by an ODE solver for a given ODE function callable. - """ - # determine the number of timesteps and set the timestep - num_timesteps = ( - int(np.ceil(evolution_time / 0.01)) - if self._num_timesteps is None - else self._num_timesteps - ) - - if self._ode_solver == ForwardEulerSolver: - solve = partial(solve_ivp, num_t_steps=num_timesteps) - else: - solve = solve_ivp - - sol = solve( - self._ode_function, - (0, evolution_time), - self._init_params, - method=self._ode_solver, - ) - - param_vals = sol.y.T - time_points = sol.t - final_param_vals = param_vals[-1] - - return final_param_vals, param_vals, time_points diff --git a/qiskit/algorithms/time_evolvers/variational/solvers/var_qte_linear_solver.py b/qiskit/algorithms/time_evolvers/variational/solvers/var_qte_linear_solver.py deleted file mode 100644 index ec06fba0a685..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/solvers/var_qte_linear_solver.py +++ /dev/null @@ -1,129 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Class for solving linear equations for Quantum Time Evolution.""" -from __future__ import annotations - -from collections.abc import Mapping, Sequence, Callable - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter -from qiskit.quantum_info import SparsePauliOp -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ..variational_principles import VariationalPrinciple - - -class VarQTELinearSolver: - """Class for solving linear equations for Quantum Time Evolution.""" - - def __init__( - self, - var_principle: VariationalPrinciple, - hamiltonian: BaseOperator, - ansatz: QuantumCircuit, - gradient_params: Sequence[Parameter] | None = None, - t_param: Parameter | None = None, - lse_solver: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, - imag_part_tol: float = 1e-7, - ) -> None: - """ - Args: - var_principle: Variational Principle to be used. - hamiltonian: Operator used for Variational Quantum Time Evolution. - ansatz: Quantum state in the form of a parametrized quantum circuit. - gradient_params: List of parameters with respect to which gradients should be computed. - If ``None`` given, gradients w.r.t. all parameters will be computed. - t_param: Time parameter in case of a time-dependent Hamiltonian. - lse_solver: Linear system of equations solver callable. It accepts ``A`` and ``b`` to - solve ``Ax=b`` and returns ``x``. If ``None``, the default ``np.linalg.lstsq`` - solver is used. - imag_part_tol: Allowed value of an imaginary part that can be neglected if no - imaginary part is expected. - - Raises: - TypeError: If t_param is provided and Hamiltonian is not of type SparsePauliOp. - """ - self._var_principle = var_principle - self._hamiltonian = hamiltonian - self._ansatz = ansatz - self._gradient_params = gradient_params - self._bind_params = gradient_params - self._time_param = t_param - self.lse_solver = lse_solver - self._imag_part_tol = imag_part_tol - - if self._time_param is not None and not isinstance(self._hamiltonian, SparsePauliOp): - raise TypeError( - f"A time parameter {t_param} has been specified, so a time-dependent " - f"hamiltonian is expected. The operator provided is of type {type(self._hamiltonian)}, " - f"which might not support parametrization. " - f"Please provide the parametrized hamiltonian as a SparsePauliOp." - ) - - @property - def lse_solver(self) -> Callable[[np.ndarray, np.ndarray], np.ndarray]: - """Returns an LSE solver callable.""" - return self._lse_solver - - @lse_solver.setter - def lse_solver(self, lse_solver: Callable[[np.ndarray, np.ndarray], np.ndarray] | None) -> None: - """Sets an LSE solver. Uses a ``np.linalg.lstsq`` callable if ``None`` provided.""" - if lse_solver is None: - lse_solver = lambda a, b: np.linalg.lstsq(a, b, rcond=1e-2)[0] - - self._lse_solver = lse_solver - - def solve_lse( - self, - param_dict: Mapping[Parameter, float], - time_value: float | None = None, - ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: - """ - Solve the system of linear equations underlying McLachlan's variational principle for the - calculation without error bounds. - - Args: - param_dict: Dictionary which relates parameter values to the parameters in the ansatz. - time_value: Time value that will be bound to ``t_param``. It is required if ``t_param`` - is not ``None``. - - Returns: - Solution to the LSE, A from Ax=b, b from Ax=b. - - Raises: - ValueError: If no time value is provided for time dependent hamiltonians. - - """ - param_values = list(param_dict.values()) - metric_tensor_lse_lhs = self._var_principle.metric_tensor(self._ansatz, param_values) - hamiltonian = self._hamiltonian - - if self._time_param is not None: - if time_value is not None: - hamiltonian = hamiltonian.assign_parameters([time_value]) - else: - raise ValueError( - "Providing a time_value is required for time-dependent hamiltonians, " - f"but got time_value = {time_value}. " - "Please provide a time_value to the solve_lse method." - ) - - evolution_grad_lse_rhs = self._var_principle.evolution_gradient( - hamiltonian, self._ansatz, param_values, self._gradient_params - ) - - x = self._lse_solver(metric_tensor_lse_lhs, evolution_grad_lse_rhs) - - return np.real(x), metric_tensor_lse_lhs, evolution_grad_lse_rhs diff --git a/qiskit/algorithms/time_evolvers/variational/var_qite.py b/qiskit/algorithms/time_evolvers/variational/var_qite.py deleted file mode 100644 index 4200389c83cf..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/var_qite.py +++ /dev/null @@ -1,120 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Variational Quantum Imaginary Time Evolution algorithm.""" -from __future__ import annotations - -from collections.abc import Mapping, Sequence -from typing import Type, Callable - -import numpy as np -from scipy.integrate import OdeSolver - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter -from qiskit.primitives import BaseEstimator - -from .solvers.ode.forward_euler_solver import ForwardEulerSolver - -from .variational_principles import ImaginaryVariationalPrinciple, ImaginaryMcLachlanPrinciple -from .var_qte import VarQTE - -from ..imaginary_time_evolver import ImaginaryTimeEvolver - - -class VarQITE(VarQTE, ImaginaryTimeEvolver): - """Variational Quantum Imaginary Time Evolution algorithm. - - .. code-block::python - - import numpy as np - - from qiskit.algorithms import TimeEvolutionProblem, VarQITE - from qiskit.algorithms.time_evolvers.variational import ImaginaryMcLachlanPrinciple - from qiskit.circuit.library import EfficientSU2 - from qiskit.quantum_info import SparsePauliOp, Pauli - from qiskit.primitives import Estimator - - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - ansatz = EfficientSU2(observable.num_qubits, reps=1) - init_param_values = np.ones(len(ansatz.parameters)) * np.pi/2 - var_principle = ImaginaryMcLachlanPrinciple() - time = 1 - - # without evaluating auxiliary operators - evolution_problem = TimeEvolutionProblem(observable, time) - var_qite = VarQITE(ansatz, init_param_values, var_principle) - evolution_result = var_qite.evolve(evolution_problem) - - # evaluating auxiliary operators - aux_ops = [Pauli("XX"), Pauli("YZ")] - evolution_problem = TimeEvolutionProblem(observable, time, aux_operators=aux_ops) - var_qite = VarQITE(ansatz, init_param_values, var_principle, Estimator()) - evolution_result = var_qite.evolve(evolution_problem) - """ - - def __init__( - self, - ansatz: QuantumCircuit, - initial_parameters: Mapping[Parameter, float] | Sequence[float], - variational_principle: ImaginaryVariationalPrinciple | None = None, - estimator: BaseEstimator | None = None, - ode_solver: Type[OdeSolver] | str = ForwardEulerSolver, - lse_solver: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, - num_timesteps: int | None = None, - imag_part_tol: float = 1e-7, - num_instability_tol: float = 1e-7, - ) -> None: - r""" - Args: - ansatz: Ansatz to be used for variational time evolution. - initial_parameters: Initial parameter values for the ansatz. - variational_principle: Variational Principle to be used. Defaults to - ``ImaginaryMcLachlanPrinciple``. - estimator: An estimator primitive used for calculating expectation values of - TimeEvolutionProblem.aux_operators. - ode_solver: ODE solver callable that implements a SciPy ``OdeSolver`` interface or a - string indicating a valid method offered by SciPy. - lse_solver: Linear system of equations solver callable. It accepts ``A`` and ``b`` to - solve ``Ax=b`` and returns ``x``. If ``None``, the default ``np.linalg.lstsq`` - solver is used. - num_timesteps: The number of timesteps to take. If ``None``, it is - automatically selected to achieve a timestep of approximately 0.01. Only - relevant in case of the ``ForwardEulerSolver``. - imag_part_tol: Allowed value of an imaginary part that can be neglected if no - imaginary part is expected. - num_instability_tol: The amount of negative value that is allowed to be - rounded up to 0 for quantities that are expected to be non-negative. - """ - if variational_principle is None: - variational_principle = ImaginaryMcLachlanPrinciple() - super().__init__( - ansatz, - initial_parameters, - variational_principle, - estimator, - ode_solver, - lse_solver=lse_solver, - num_timesteps=num_timesteps, - imag_part_tol=imag_part_tol, - num_instability_tol=num_instability_tol, - ) diff --git a/qiskit/algorithms/time_evolvers/variational/var_qrte.py b/qiskit/algorithms/time_evolvers/variational/var_qrte.py deleted file mode 100644 index f8305f643cb5..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/var_qrte.py +++ /dev/null @@ -1,122 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Variational Quantum Real Time Evolution algorithm.""" -from __future__ import annotations - -from collections.abc import Mapping, Sequence -from typing import Type, Callable - -import numpy as np -from scipy.integrate import OdeSolver - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter -from qiskit.primitives import BaseEstimator - -from .solvers.ode.forward_euler_solver import ForwardEulerSolver - -from .variational_principles import RealVariationalPrinciple, RealMcLachlanPrinciple -from .var_qte import VarQTE - -from ..real_time_evolver import RealTimeEvolver - - -class VarQRTE(VarQTE, RealTimeEvolver): - """Variational Quantum Real Time Evolution algorithm. - - .. code-block::python - - import numpy as np - - from qiskit.algorithms import TimeEvolutionProblem, VarQRTE - from qiskit.circuit.library import EfficientSU2 - from qiskit.algorithms.time_evolvers.variational import RealMcLachlanPrinciple - from qiskit.quantum_info import SparsePauliOp - from qiskit.quantum_info import SparsePauliOp, Pauli - from qiskit.primitives import Estimator - - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - ansatz = EfficientSU2(observable.num_qubits, reps=1) - init_param_values = np.ones(len(ansatz.parameters)) * np.pi/2 - var_principle = RealMcLachlanPrinciple() - time = 1 - - # without evaluating auxiliary operators - evolution_problem = TimeEvolutionProblem(observable, time) - var_qrte = VarQRTE(ansatz, init_param_values, var_principle) - evolution_result = var_qrte.evolve(evolution_problem) - - # evaluating auxiliary operators - aux_ops = [Pauli("XX"), Pauli("YZ")] - evolution_problem = TimeEvolutionProblem(observable, time, aux_operators=aux_ops) - var_qrte = VarQRTE(ansatz, init_param_values, var_principle, Estimator()) - evolution_result = var_qrte.evolve(evolution_problem) - """ - - def __init__( - self, - ansatz: QuantumCircuit, - initial_parameters: Mapping[Parameter, float] | Sequence[float], - variational_principle: RealVariationalPrinciple | None = None, - estimator: BaseEstimator | None = None, - ode_solver: Type[OdeSolver] | str = ForwardEulerSolver, - lse_solver: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, - num_timesteps: int | None = None, - imag_part_tol: float = 1e-7, - num_instability_tol: float = 1e-7, - ) -> None: - r""" - Args: - ansatz: Ansatz to be used for variational time evolution. - initial_parameters: Initial parameter values for an ansatz. - variational_principle: Variational Principle to be used. Defaults to - ``RealMcLachlanPrinciple``. - estimator: An estimator primitive used for calculating expectation values of - TimeEvolutionProblem.aux_operators. - ode_solver: ODE solver callable that implements a SciPy ``OdeSolver`` interface or a - string indicating a valid method offered by SciPy. - lse_solver: Linear system of equations solver callable. It accepts ``A`` and ``b`` to - solve ``Ax=b`` and returns ``x``. If ``None``, the default ``np.linalg.lstsq`` - solver is used. - num_timesteps: The number of timesteps to take. If ``None``, it is - automatically selected to achieve a timestep of approximately 0.01. Only - relevant in case of the ``ForwardEulerSolver``. - imag_part_tol: Allowed value of an imaginary part that can be neglected if no - imaginary part is expected. - num_instability_tol: The amount of negative value that is allowed to be - rounded up to 0 for quantities that are expected to be - non-negative. - """ - if variational_principle is None: - variational_principle = RealMcLachlanPrinciple() - super().__init__( - ansatz, - initial_parameters, - variational_principle, - estimator, - ode_solver, - lse_solver=lse_solver, - num_timesteps=num_timesteps, - imag_part_tol=imag_part_tol, - num_instability_tol=num_instability_tol, - ) diff --git a/qiskit/algorithms/time_evolvers/variational/var_qte.py b/qiskit/algorithms/time_evolvers/variational/var_qte.py deleted file mode 100644 index f0d33a6b8ae5..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/var_qte.py +++ /dev/null @@ -1,290 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Variational Quantum Time Evolution Interface""" -from __future__ import annotations - -from abc import ABC -from collections.abc import Mapping, Callable, Sequence -from typing import Type - -import numpy as np -from scipy.integrate import OdeSolver - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter -from qiskit.opflow import PauliSumOp -from qiskit.primitives import BaseEstimator -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from .solvers.ode.forward_euler_solver import ForwardEulerSolver -from .solvers.ode.ode_function_factory import OdeFunctionFactory -from .solvers.ode.var_qte_ode_solver import VarQTEOdeSolver -from .solvers.var_qte_linear_solver import VarQTELinearSolver - -from .variational_principles.variational_principle import VariationalPrinciple -from .var_qte_result import VarQTEResult - -from ..time_evolution_problem import TimeEvolutionProblem - -from ...observables_evaluator import estimate_observables - - -class VarQTE(ABC): - """Variational Quantum Time Evolution. - - Algorithms that use variational principles to compute a time evolution for a given - Hermitian operator (Hamiltonian) and a quantum state prepared by a parameterized quantum - circuit. - - Attributes: - ansatz (QuantumCircuit): Ansatz to be used for variational time evolution. - initial_parameters (Mapping[Parameter, float] | Sequence[float]): Initial - parameter values for an ansatz. - variational_principle (VariationalPrinciple): Variational Principle to be used. - estimator (BaseEstimator): An estimator primitive used for calculating expectation - values of ``TimeEvolutionProblem.aux_operators``. - ode_solver(Type[OdeSolver] | str): ODE solver callable that implements a SciPy - ``OdeSolver`` interface or a string indicating a valid method offered by SciPy. - lse_solver (Callable[[np.ndarray, np.ndarray], np.ndarray] | None): Linear system - of equations solver callable. It accepts ``A`` and ``b`` to solve ``Ax=b`` - and returns ``x``. - num_timesteps (int | None): The number of timesteps to take. If None, it is - automatically selected to achieve a timestep of approximately 0.01. Only - relevant in case of the ``ForwardEulerSolver``. - imag_part_tol (float): Allowed value of an imaginary part that can be neglected if no - imaginary part is expected. - num_instability_tol (float): The amount of negative value that is allowed to be - rounded up to 0 for quantities that are expected to be - non-negative. - References: - - [1] Benjamin, Simon C. et al. (2019). - Theory of variational quantum simulation. ``_ - """ - - def __init__( - self, - ansatz: QuantumCircuit, - initial_parameters: Mapping[Parameter, float] | Sequence[float], - variational_principle: VariationalPrinciple, - estimator: BaseEstimator, - ode_solver: Type[OdeSolver] | str = ForwardEulerSolver, - lse_solver: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None, - num_timesteps: int | None = None, - imag_part_tol: float = 1e-7, - num_instability_tol: float = 1e-7, - ) -> None: - r""" - Args: - ansatz: Ansatz to be used for variational time evolution. - initial_parameters: Initial parameter values for an ansatz. - variational_principle: Variational Principle to be used. - estimator: An estimator primitive used for calculating expectation values of - TimeEvolutionProblem.aux_operators. - ode_solver: ODE solver callable that implements a SciPy ``OdeSolver`` interface or a - string indicating a valid method offered by SciPy. - lse_solver: Linear system of equations solver callable. It accepts ``A`` and ``b`` to - solve ``Ax=b`` and returns ``x``. - num_timesteps: The number of timesteps to take. If None, it is - automatically selected to achieve a timestep of approximately 0.01. Only - relevant in case of the ``ForwardEulerSolver``. - imag_part_tol: Allowed value of an imaginary part that can be neglected if no - imaginary part is expected. - num_instability_tol: The amount of negative value that is allowed to be - rounded up to 0 for quantities that are expected to be - non-negative. - """ - super().__init__() - self.ansatz = ansatz - self.initial_parameters = initial_parameters - self.variational_principle = variational_principle - self.estimator = estimator - self.num_timesteps = num_timesteps - self.lse_solver = lse_solver - self.ode_solver = ode_solver - self.imag_part_tol = imag_part_tol - self.num_instability_tol = num_instability_tol - # OdeFunction abstraction kept for potential extensions - unclear at the moment; - # currently hidden from the user - self._ode_function_factory = OdeFunctionFactory() - - def evolve(self, evolution_problem: TimeEvolutionProblem) -> VarQTEResult: - """Apply Variational Quantum Time Evolution to the given operator. - - Args: - evolution_problem: Instance defining an evolution problem. - Returns: - Result of the evolution which includes a quantum circuit with bound parameters as an - evolved state and, if provided, observables evaluated on the evolved state. - - Raises: - ValueError: If ``initial_state`` is included in the ``evolution_problem``. - """ - self._validate_aux_ops(evolution_problem) - - if evolution_problem.initial_state is not None: - raise ValueError( - "An initial_state was provided to the TimeEvolutionProblem but this is not " - "supported by VarQTE. Please remove this state from the problem definition " - "and set VarQTE.initial_parameters with the corresponding initial parameter " - "values instead." - ) - - init_state_param_dict = self._create_init_state_param_dict( - self.initial_parameters, self.ansatz.parameters - ) - - # unwrap PauliSumOp (in the future this will be deprecated) - if isinstance(evolution_problem.hamiltonian, PauliSumOp): - hamiltonian = ( - evolution_problem.hamiltonian.primitive * evolution_problem.hamiltonian.coeff - ) - else: - hamiltonian = evolution_problem.hamiltonian - - evolved_state, param_values, time_points = self._evolve( - init_state_param_dict, - hamiltonian, - evolution_problem.time, - evolution_problem.t_param, - ) - - observables = [] - if evolution_problem.aux_operators is not None: - for values in param_values: - # cannot batch evaluation because estimate_observables - # only accepts single circuits - evol_state = self.ansatz.assign_parameters( - dict(zip(init_state_param_dict.keys(), values)) - ) - observable = estimate_observables( - self.estimator, - evol_state, - evolution_problem.aux_operators, - ) - observables.append(observable) - - # TODO: deprecate returning evaluated_aux_ops. - # As these are the observables for the last time step. - evaluated_aux_ops = observables[-1] if len(observables) > 0 else None - - return VarQTEResult( - evolved_state, evaluated_aux_ops, observables, time_points, param_values - ) - - def _evolve( - self, - init_state_param_dict: Mapping[Parameter, float], - hamiltonian: BaseOperator, - time: float, - t_param: Parameter | None = None, - ) -> tuple[QuantumCircuit | None, Sequence[Sequence[float]], Sequence[float]]: - r""" - Helper method for performing time evolution. Works both for imaginary and real case. - - Args: - init_state_param_dict: Parameter dictionary with initial values for a given - parametrized state/ansatz. - hamiltonian: Operator used for Variational Quantum Time Evolution (VarQTE). - time: Total time of evolution. - t_param: Time parameter in case of a time-dependent Hamiltonian. - - Returns: - Result of the evolution which is a quantum circuit with bound parameters as an - evolved state. - """ - - init_state_parameters = list(init_state_param_dict.keys()) - init_state_parameter_values = list(init_state_param_dict.values()) - - linear_solver = VarQTELinearSolver( - self.variational_principle, - hamiltonian, - self.ansatz, - init_state_parameters, - t_param, - self.lse_solver, - self.imag_part_tol, - ) - - # Convert the operator that holds the Hamiltonian and ansatz into a NaturalGradient operator - ode_function = self._ode_function_factory._build( - linear_solver, init_state_param_dict, t_param - ) - - ode_solver = VarQTEOdeSolver( - init_state_parameter_values, ode_function, self.ode_solver, self.num_timesteps - ) - final_param_values, param_values, time_points = ode_solver.run(time) - param_dict_from_ode = dict(zip(init_state_parameters, final_param_values)) - - return self.ansatz.assign_parameters(param_dict_from_ode), param_values, time_points - - @staticmethod - def _create_init_state_param_dict( - param_values: Mapping[Parameter, float] | Sequence[float], - init_state_parameters: Sequence[Parameter], - ) -> Mapping[Parameter, float]: - r""" - If ``param_values`` is a dictionary, it looks for parameters present in an initial state - (an ansatz) in a ``param_values``. Based on that, it creates a new dictionary containing - only parameters present in an initial state and their respective values. - If ``param_values`` is a list of values, it creates a new dictionary containing - parameters present in an initial state and their respective values. - - Args: - param_values: Dictionary which relates parameter values to the parameters or a list of - values. - init_state_parameters: Parameters present in a quantum state. - - Returns: - Dictionary that maps parameters of an initial state to some values. - - Raises: - ValueError: If the dictionary with parameter values provided does not include all - parameters present in the initial state or if the list of values provided is not the - same length as the list of parameters. - TypeError: If an unsupported type of ``param_values`` provided. - """ - if isinstance(param_values, Mapping): - init_state_parameter_values: Sequence[float] = [] - for param in init_state_parameters: - if param in param_values.keys(): - init_state_parameter_values.append(param_values[param]) - else: - raise ValueError( - f"The dictionary with parameter values provided does not " - f"include all parameters present in the initial state." - f"Parameters present in the state: {init_state_parameters}, " - f"parameters in the dictionary: " - f"{list(param_values.keys())}." - ) - elif isinstance(param_values, (Sequence, np.ndarray)): - if len(init_state_parameters) != len(param_values): - raise ValueError( - f"Initial state has {len(init_state_parameters)} parameters and the" - f" list of values has {len(param_values)} elements. They should be" - f" equal in length." - ) - init_state_parameter_values = param_values - else: - raise TypeError(f"Unsupported type of param_values provided: {type(param_values)}.") - - init_state_param_dict = dict(zip(init_state_parameters, init_state_parameter_values)) - return init_state_param_dict - - def _validate_aux_ops(self, evolution_problem: TimeEvolutionProblem) -> None: - if evolution_problem.aux_operators is not None and self.estimator is None: - raise ValueError( - "aux_operators were provided for evaluations but no ``estimator`` was provided." - ) diff --git a/qiskit/algorithms/time_evolvers/variational/var_qte_result.py b/qiskit/algorithms/time_evolvers/variational/var_qte_result.py deleted file mode 100644 index 3efb5e7c1789..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/var_qte_result.py +++ /dev/null @@ -1,56 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Result object for varQTE.""" -from __future__ import annotations - -import numpy as np - -from qiskit.circuit import QuantumCircuit - -from ..time_evolution_result import TimeEvolutionResult - -from ...list_or_dict import ListOrDict - - -class VarQTEResult(TimeEvolutionResult): - """The result object for the variational quantum time evolution algorithms. - - Attributes: - parameter_values (np.array | None): Optional list of parameter values obtained after - each evolution step. - """ - - def __init__( - self, - evolved_state: QuantumCircuit, - aux_ops_evaluated: ListOrDict[tuple[complex, complex]] | None = None, - observables: ListOrDict[tuple[np.ndarray, np.ndarray]] | None = None, - times: np.ndarray | None = None, - parameter_values: np.ndarray | None = None, - ): - """ - Args: - evolved_state: An evolved quantum state. - aux_ops_evaluated: Optional list of observables for which expected values on an evolved - state are calculated. These values are in fact tuples formatted as (mean, standard - deviation). - observables: Optional list of observables for which expected on an evolved state are - calculated at each timestep. - These values are in fact lists of tuples formatted as (mean, standard deviation). - times: Optional list of times at which each observable has been evaluated. - parameter_values: Optional list of parameter values obtained after each evolution step. - - """ - - super().__init__(evolved_state, aux_ops_evaluated, observables, times) - self.parameter_values = parameter_values diff --git a/qiskit/algorithms/time_evolvers/variational/variational_principles/__init__.py b/qiskit/algorithms/time_evolvers/variational/variational_principles/__init__.py deleted file mode 100644 index be04c03d7bcf..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/variational_principles/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Variational Principles""" - -from .variational_principle import VariationalPrinciple -from .imaginary_mc_lachlan_principle import ImaginaryMcLachlanPrinciple -from .imaginary_variational_principle import ImaginaryVariationalPrinciple -from .real_mc_lachlan_principle import RealMcLachlanPrinciple -from .real_variational_principle import RealVariationalPrinciple - -__all__ = [ - "VariationalPrinciple", - "ImaginaryMcLachlanPrinciple", - "ImaginaryVariationalPrinciple", - "RealMcLachlanPrinciple", - "RealVariationalPrinciple", -] diff --git a/qiskit/algorithms/time_evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py b/qiskit/algorithms/time_evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py deleted file mode 100644 index 09e1e03d3473..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/variational_principles/imaginary_mc_lachlan_principle.py +++ /dev/null @@ -1,128 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Class for an Imaginary McLachlan's Variational Principle.""" -from __future__ import annotations - -import warnings - -from collections.abc import Sequence - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter -from qiskit.primitives import Estimator -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from .imaginary_variational_principle import ImaginaryVariationalPrinciple - -from ....exceptions import AlgorithmError -from ....gradients import ( - BaseEstimatorGradient, - BaseQGT, - DerivativeType, - LinCombQGT, - LinCombEstimatorGradient, -) - - -class ImaginaryMcLachlanPrinciple(ImaginaryVariationalPrinciple): - """Class for an Imaginary McLachlan's Variational Principle. It aims to minimize the distance - between both sides of the Wick-rotated Schrödinger equation with a quantum state given as a - parametrized trial state. The principle leads to a system of linear equations handled by a - linear solver. The imaginary variant means that we consider imaginary time dynamics. - """ - - def __init__( - self, - qgt: BaseQGT | None = None, - gradient: BaseEstimatorGradient | None = None, - ) -> None: - """ - Args: - qgt: Instance of a the GQT class used to compute the QFI. - If ``None`` provided, ``LinCombQGT`` is used. - gradient: Instance of a class used to compute the state gradient. - If ``None`` provided, ``LinCombEstimatorGradient`` is used. - - Raises: - AlgorithmError: If the gradient instance does not contain an estimator. - """ - - self._validate_grad_settings(gradient) - - if gradient is not None: - try: - estimator = gradient._estimator - except Exception as exc: - raise AlgorithmError( - "The provided gradient instance does not contain an estimator primitive." - ) from exc - else: - estimator = Estimator() - gradient = LinCombEstimatorGradient(estimator) - - if qgt is None: - qgt = LinCombQGT(estimator) - - super().__init__(qgt, gradient) - - def evolution_gradient( - self, - hamiltonian: BaseOperator, - ansatz: QuantumCircuit, - param_values: Sequence[float], - gradient_params: Sequence[Parameter] | None = None, - ) -> np.ndarray: - """ - Calculates an evolution gradient according to the rules of this variational principle. - - Args: - hamiltonian: Operator used for Variational Quantum Time Evolution. - ansatz: Quantum state in the form of a parametrized quantum circuit. - param_values: Values of parameters to be bound. - gradient_params: List of parameters with respect to which gradients should be computed. - If ``None`` given, gradients w.r.t. all parameters will be computed. - - Returns: - An evolution gradient. - - Raises: - AlgorithmError: If a gradient job fails. - """ - - try: - evolution_grad_lse_rhs = ( - self.gradient.run([ansatz], [hamiltonian], [param_values], [gradient_params]) - .result() - .gradients[0] - ) - - except Exception as exc: - raise AlgorithmError("The gradient primitive job failed!") from exc - - return -0.5 * evolution_grad_lse_rhs - - @staticmethod - def _validate_grad_settings(gradient): - if ( - gradient is not None - and hasattr(gradient, "_derivative_type") - and gradient._derivative_type != DerivativeType.REAL - ): - warnings.warn( - "A gradient instance with a setting for calculating imaginary part of " - "the gradient was provided. This variational principle requires the" - "real part. The setting to real was changed automatically." - ) - gradient._derivative_type = DerivativeType.REAL diff --git a/qiskit/algorithms/time_evolvers/variational/variational_principles/imaginary_variational_principle.py b/qiskit/algorithms/time_evolvers/variational/variational_principles/imaginary_variational_principle.py deleted file mode 100644 index 1255e52b7c65..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/variational_principles/imaginary_variational_principle.py +++ /dev/null @@ -1,22 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Abstract class for an Imaginary Variational Principle.""" - -from abc import ABC - -from .variational_principle import VariationalPrinciple - - -class ImaginaryVariationalPrinciple(VariationalPrinciple, ABC): - """Abstract class for an Imaginary Variational Principle. The imaginary variant - means that we consider imaginary time dynamics.""" diff --git a/qiskit/algorithms/time_evolvers/variational/variational_principles/real_mc_lachlan_principle.py b/qiskit/algorithms/time_evolvers/variational/variational_principles/real_mc_lachlan_principle.py deleted file mode 100644 index d7a946b8ab70..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/variational_principles/real_mc_lachlan_principle.py +++ /dev/null @@ -1,166 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Class for a Real McLachlan's Variational Principle.""" -from __future__ import annotations - -import warnings - -from collections.abc import Sequence - -import numpy as np -from numpy import real - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter -from qiskit.primitives import Estimator -from qiskit.quantum_info import SparsePauliOp -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from .real_variational_principle import RealVariationalPrinciple - -from ....exceptions import AlgorithmError -from ....gradients import ( - BaseEstimatorGradient, - BaseQGT, - DerivativeType, - LinCombQGT, - LinCombEstimatorGradient, -) - - -class RealMcLachlanPrinciple(RealVariationalPrinciple): - """Class for a Real McLachlan's Variational Principle. It aims to minimize the distance - between both sides of the Schrödinger equation with a quantum state given as a parametrized - trial state. The principle leads to a system of linear equations handled by a linear solver. - The real variant means that we consider real time dynamics. - """ - - def __init__( - self, - qgt: BaseQGT | None = None, - gradient: BaseEstimatorGradient | None = None, - ) -> None: - """ - Args: - qgt: Instance of a the GQT class used to compute the QFI. - If ``None`` provided, ``LinCombQGT`` is used. - gradient: Instance of a class used to compute the state gradient. - If ``None`` provided, ``LinCombEstimatorGradient`` is used. - - Raises: - AlgorithmError: If the gradient instance does not contain an estimator. - """ - self._validate_grad_settings(gradient) - - if gradient is not None: - try: - estimator = gradient._estimator - except Exception as exc: - raise AlgorithmError( - "The provided gradient instance does not contain an estimator primitive." - ) from exc - else: - estimator = Estimator() - gradient = LinCombEstimatorGradient(estimator, derivative_type=DerivativeType.IMAG) - - if qgt is None: - qgt = LinCombQGT(estimator) - - super().__init__(qgt, gradient) - - def evolution_gradient( - self, - hamiltonian: BaseOperator, - ansatz: QuantumCircuit, - param_values: Sequence[float], - gradient_params: Sequence[Parameter] | None = None, - ) -> np.ndarray: - """ - Calculates an evolution gradient according to the rules of this variational principle. - - Args: - hamiltonian: Operator used for Variational Quantum Time Evolution. - ansatz: Quantum state in the form of a parametrized quantum circuit. - param_values: Values of parameters to be bound. - gradient_params: List of parameters with respect to which gradients should be computed. - If ``None`` given, gradients w.r.t. all parameters will be computed. - - Returns: - An evolution gradient. - - Raises: - AlgorithmError: If a gradient job fails. - """ - - try: - estimator_job = self.gradient._estimator.run([ansatz], [hamiltonian], [param_values]) - energy = estimator_job.result().values[0] - except Exception as exc: - raise AlgorithmError("The primitive job failed!") from exc - - modified_hamiltonian = self._construct_modified_hamiltonian(hamiltonian, real(energy)) - - try: - evolution_grad = ( - 0.5 - * self.gradient.run( - [ansatz], - [modified_hamiltonian], - parameters=[gradient_params], - parameter_values=[param_values], - ) - .result() - .gradients[0] - ) - except Exception as exc: - raise AlgorithmError("The gradient primitive job failed!") from exc - - # The BaseEstimatorGradient class returns the gradient of the opposite sign than we expect - # here (i.e. with a minus sign), hence the correction that cancels it to recover the - # real McLachlan's principle equations that do not have a minus sign. - evolution_grad = (-1) * evolution_grad - return evolution_grad - - @staticmethod - def _construct_modified_hamiltonian(hamiltonian: BaseOperator, energy: float) -> BaseOperator: - """ - Modifies a Hamiltonian according to the rules of this variational principle. - - Args: - hamiltonian: Operator used for Variational Quantum Time Evolution. - energy: The energy correction value. - - Returns: - A modified Hamiltonian. - """ - energy_term = SparsePauliOp.from_list( - hamiltonian.to_list() + [("I" * hamiltonian.num_qubits, -energy)] - ) - return energy_term - - @staticmethod - def _validate_grad_settings(gradient): - - if gradient is not None: - if not hasattr(gradient, "_derivative_type"): - raise ValueError( - "The gradient instance provided does not support calculating imaginary part. " - "Please choose a different gradient class." - ) - if gradient._derivative_type != DerivativeType.IMAG: - warnings.warn( - "A gradient instance with a setting for calculating real part of the" - "gradient was provided. This variational principle requires the" - "imaginary part. The setting to imaginary was changed automatically." - ) - gradient._derivative_type = DerivativeType.IMAG diff --git a/qiskit/algorithms/time_evolvers/variational/variational_principles/real_variational_principle.py b/qiskit/algorithms/time_evolvers/variational/variational_principles/real_variational_principle.py deleted file mode 100644 index a93c50675e3e..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/variational_principles/real_variational_principle.py +++ /dev/null @@ -1,22 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Class for a Real Variational Principle.""" - -from abc import ABC - -from .variational_principle import VariationalPrinciple - - -class RealVariationalPrinciple(VariationalPrinciple, ABC): - """Class for a Real Variational Principle. The real variant - means that we consider real time dynamics.""" diff --git a/qiskit/algorithms/time_evolvers/variational/variational_principles/variational_principle.py b/qiskit/algorithms/time_evolvers/variational/variational_principles/variational_principle.py deleted file mode 100644 index be16849155c4..000000000000 --- a/qiskit/algorithms/time_evolvers/variational/variational_principles/variational_principle.py +++ /dev/null @@ -1,98 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Class for a Variational Principle.""" -from __future__ import annotations - -from abc import ABC, abstractmethod -from collections.abc import Sequence - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter -from qiskit.quantum_info.operators.base_operator import BaseOperator - -from ....exceptions import AlgorithmError -from ....gradients import BaseEstimatorGradient, BaseQGT, DerivativeType - - -class VariationalPrinciple(ABC): - """A Variational Principle class. It determines the time propagation of parameters in a - quantum state provided as a parametrized quantum circuit (ansatz). - - Attributes: - qgt (BaseQGT): Instance of a class used to compute the GQT. - gradient (BaseEstimatorGradient): Instance of a class used to compute the - state gradient. - """ - - def __init__( - self, - qgt: BaseQGT, - gradient: BaseEstimatorGradient, - ) -> None: - """ - Args: - qgt: Instance of a class used to compute the GQT. - gradient: Instance of a class used to compute the state gradient. - """ - self.qgt = qgt - self.gradient = gradient - - def metric_tensor( - self, ansatz: QuantumCircuit, param_values: Sequence[float] - ) -> Sequence[float]: - """ - Calculates a metric tensor according to the rules of this variational principle. - - Args: - ansatz: Quantum state in the form of a parametrized quantum circuit. - param_values: Values of parameters to be bound. - - Returns: - Metric tensor. - - Raises: - AlgorithmError: If a QFI job fails. - """ - - self.qgt.derivative_type = DerivativeType.REAL - try: - metric_tensor = self.qgt.run([ansatz], [param_values], [None]).result().qgts[0] - except Exception as exc: - - raise AlgorithmError("The QFI primitive job failed!") from exc - return metric_tensor - - @abstractmethod - def evolution_gradient( - self, - hamiltonian: BaseOperator, - ansatz: QuantumCircuit, - param_values: Sequence[float], - gradient_params: Sequence[Parameter] | None = None, - ) -> np.ndarray: - """ - Calculates an evolution gradient according to the rules of this variational principle. - - Args: - hamiltonian: Operator used for Variational Quantum Time Evolution. - ansatz: Quantum state in the form of a parametrized quantum circuit. - param_values: Values of parameters to be bound. - gradient_params: List of parameters with respect to which gradients should be computed. - If ``None`` given, gradients w.r.t. all parameters will be computed. - - Returns: - An evolution gradient. - """ - pass diff --git a/qiskit/algorithms/utils/__init__.py b/qiskit/algorithms/utils/__init__.py deleted file mode 100644 index 2b49396270c7..000000000000 --- a/qiskit/algorithms/utils/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Common Qiskit algorithms utility functions.""" - -from .validate_initial_point import validate_initial_point -from .validate_bounds import validate_bounds - -__all__ = [ - "validate_initial_point", - "validate_bounds", -] diff --git a/qiskit/algorithms/utils/set_batching.py b/qiskit/algorithms/utils/set_batching.py deleted file mode 100644 index 225f50a6fed8..000000000000 --- a/qiskit/algorithms/utils/set_batching.py +++ /dev/null @@ -1,27 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Set default batch sizes for the optimizers.""" - -from qiskit.algorithms.optimizers import Optimizer, SPSA - - -def _set_default_batchsize(optimizer: Optimizer) -> bool: - """Set the default batchsize, if None is set and return whether it was updated or not.""" - if isinstance(optimizer, SPSA): - updated = optimizer._max_evals_grouped is None - if updated: - optimizer.set_max_evals_grouped(50) - else: # we only set a batchsize for SPSA - updated = False - - return updated diff --git a/qiskit/algorithms/utils/validate_bounds.py b/qiskit/algorithms/utils/validate_bounds.py deleted file mode 100644 index 747e68f78a52..000000000000 --- a/qiskit/algorithms/utils/validate_bounds.py +++ /dev/null @@ -1,44 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Validate parameter bounds.""" - -from __future__ import annotations - -from qiskit.circuit import QuantumCircuit - - -def validate_bounds(circuit: QuantumCircuit) -> list[tuple[float | None, float | None]]: - """ - Validate the bounds provided by a quantum circuit against its number of parameters. - If no bounds are obtained, return ``None`` for all lower and upper bounds. - - Args: - circuit: A parameterized quantum circuit. - - Returns: - A list of tuples (lower_bound, upper_bound)). - - Raises: - ValueError: If the number of bounds does not the match the number of circuit parameters. - """ - if hasattr(circuit, "parameter_bounds") and circuit.parameter_bounds is not None: - bounds = circuit.parameter_bounds - if len(bounds) != circuit.num_parameters: - raise ValueError( - f"The number of bounds ({len(bounds)}) does not match the number of " - f"parameters in the circuit ({circuit.num_parameters})." - ) - else: - bounds = [(None, None)] * circuit.num_parameters - - return bounds diff --git a/qiskit/algorithms/utils/validate_initial_point.py b/qiskit/algorithms/utils/validate_initial_point.py deleted file mode 100644 index 56a7654a16e4..000000000000 --- a/qiskit/algorithms/utils/validate_initial_point.py +++ /dev/null @@ -1,72 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Validate an initial point.""" - -from __future__ import annotations - -import warnings -from collections.abc import Sequence - -import numpy as np - -from qiskit.circuit import QuantumCircuit -from qiskit.utils import algorithm_globals - - -def validate_initial_point( - point: Sequence[float] | None, circuit: QuantumCircuit -) -> Sequence[float]: - r""" - Validate a choice of initial point against a choice of circuit. If no point is provided, a - random point will be generated within certain parameter bounds. It will first look to the - circuit for these bounds. If the circuit does not specify bounds, bounds of :math:`-2\pi`, - :math:`2\pi` will be used. - - Args: - point: An initial point. - circuit: A parameterized quantum circuit. - - Returns: - A validated initial point. - - Raises: - ValueError: If the dimension of the initial point does not match the number of circuit - parameters. - """ - expected_size = circuit.num_parameters - - if point is None: - # get bounds if circuit has them set, otherwise use [-2pi, 2pi] for each parameter - bounds = getattr(circuit, "parameter_bounds", None) - if bounds is None: - bounds = [(-2 * np.pi, 2 * np.pi)] * expected_size - - # replace all Nones by [-2pi, 2pi] - lower_bounds = [] - upper_bounds = [] - for lower, upper in bounds: - lower_bounds.append(lower if lower is not None else -2 * np.pi) - upper_bounds.append(upper if upper is not None else 2 * np.pi) - - # sample from within bounds - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - point = algorithm_globals.random.uniform(lower_bounds, upper_bounds) - - elif len(point) != expected_size: - raise ValueError( - f"The dimension of the initial point ({len(point)}) does not match the " - f"number of parameters in the circuit ({expected_size})." - ) - - return point diff --git a/qiskit/algorithms/variational_algorithm.py b/qiskit/algorithms/variational_algorithm.py deleted file mode 100644 index 1b8b2ec6a164..000000000000 --- a/qiskit/algorithms/variational_algorithm.py +++ /dev/null @@ -1,137 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The Variational Algorithm Base Class. - -This class can be used an interface for working with Variation Algorithms, such as VQE, -QAOA, or QSVM, and also provides helper utilities for implementing new variational algorithms. -Writing a new variational algorithm is a simple as extending this class, implementing a cost -function for the new algorithm to pass to the optimizer, and running :meth:`find_minimum` method -of this class to carry out the optimization. Alternatively, all of the functions below can be -overridden to opt-out of this infrastructure but still meet the interface requirements. - -.. note:: - - This component has some function that is normally random. If you want to reproduce behavior - then you should set the random number generator seed in the algorithm_globals - (``qiskit.utils.algorithm_globals.random_seed = seed``). -""" - -from __future__ import annotations -from abc import ABC, abstractmethod -import numpy as np - -from qiskit.circuit import QuantumCircuit - -from .algorithm_result import AlgorithmResult -from .optimizers import OptimizerResult - - -class VariationalAlgorithm(ABC): - """The Variational Algorithm Base Class.""" - - @property - @abstractmethod - def initial_point(self) -> np.ndarray | None: - """Returns initial point.""" - pass - - @initial_point.setter - @abstractmethod - def initial_point(self, initial_point: np.ndarray | None) -> None: - """Sets initial point.""" - pass - - -class VariationalResult(AlgorithmResult): - """Variation Algorithm Result.""" - - def __init__(self) -> None: - super().__init__() - self._optimizer_evals: int | None = None - self._optimizer_time: float | None = None - self._optimal_value: float | None = None - self._optimal_point: np.ndarray | None = None - self._optimal_parameters: dict | None = None - self._optimizer_result: OptimizerResult | None = None - self._optimal_circuit: QuantumCircuit | None = None - - @property - def optimizer_evals(self) -> int | None: - """Returns number of optimizer evaluations""" - return self._optimizer_evals - - @optimizer_evals.setter - def optimizer_evals(self, value: int) -> None: - """Sets number of optimizer evaluations""" - self._optimizer_evals = value - - @property - def optimizer_time(self) -> float | None: - """Returns time taken for optimization""" - return self._optimizer_time - - @optimizer_time.setter - def optimizer_time(self, value: float) -> None: - """Sets time taken for optimization""" - self._optimizer_time = value - - @property - def optimal_value(self) -> float | None: - """Returns optimal value""" - return self._optimal_value - - @optimal_value.setter - def optimal_value(self, value: int) -> None: - """Sets optimal value""" - self._optimal_value = value - - @property - def optimal_point(self) -> np.ndarray | None: - """Returns optimal point""" - return self._optimal_point - - @optimal_point.setter - def optimal_point(self, value: np.ndarray) -> None: - """Sets optimal point""" - self._optimal_point = value - - @property - def optimal_parameters(self) -> dict | None: - """Returns the optimal parameters in a dictionary""" - return self._optimal_parameters - - @optimal_parameters.setter - def optimal_parameters(self, value: dict) -> None: - """Sets optimal parameters""" - self._optimal_parameters = value - - @property - def optimizer_result(self) -> OptimizerResult | None: - """Returns the optimizer result""" - return self._optimizer_result - - @optimizer_result.setter - def optimizer_result(self, value: OptimizerResult) -> None: - """Sets optimizer result""" - self._optimizer_result = value - - @property - def optimal_circuit(self) -> QuantumCircuit: - """The optimal circuits. Along with the optimal parameters, - these can be used to retrieve the minimum eigenstate. - """ - return self._optimal_circuit - - @optimal_circuit.setter - def optimal_circuit(self, optimal_circuit: QuantumCircuit) -> None: - self._optimal_circuit = optimal_circuit diff --git a/test/python/algorithms/__init__.py b/test/python/algorithms/__init__.py deleted file mode 100644 index 5303c5b1256c..000000000000 --- a/test/python/algorithms/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Algorithms test module""" - -from .algorithms_test_case import QiskitAlgorithmsTestCase - -__all__ = ["QiskitAlgorithmsTestCase"] diff --git a/test/python/algorithms/algorithms_test_case.py b/test/python/algorithms/algorithms_test_case.py deleted file mode 100644 index 8fc9effd0bf7..000000000000 --- a/test/python/algorithms/algorithms_test_case.py +++ /dev/null @@ -1,21 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Algorithms Test Case""" - -from qiskit.test import QiskitTestCase - - -class QiskitAlgorithmsTestCase(QiskitTestCase): - """Algorithms test Case""" - - pass diff --git a/test/python/algorithms/eigensolvers/__init__.py b/test/python/algorithms/eigensolvers/__init__.py deleted file mode 100644 index e2113e5c114e..000000000000 --- a/test/python/algorithms/eigensolvers/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Tests for the eigensolvers.""" diff --git a/test/python/algorithms/eigensolvers/test_numpy_eigensolver.py b/test/python/algorithms/eigensolvers/test_numpy_eigensolver.py deleted file mode 100644 index 13f2f9a67e4b..000000000000 --- a/test/python/algorithms/eigensolvers/test_numpy_eigensolver.py +++ /dev/null @@ -1,216 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test NumPyEigensolver""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -from ddt import data, ddt - -from qiskit.algorithms.eigensolvers import NumPyEigensolver -from qiskit.algorithms import AlgorithmError -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info import Operator, SparsePauliOp, Pauli, ScalarOp - -H2_SPARSE_PAULI = SparsePauliOp( - ["II", "ZI", "IZ", "ZZ", "XX"], - coeffs=[ - -1.052373245772859, - 0.39793742484318045, - -0.39793742484318045, - -0.01128010425623538, - 0.18093119978423156, - ], -) - -H2_OP = Operator(H2_SPARSE_PAULI.to_matrix()) - -H2_PAULI = PauliSumOp(H2_SPARSE_PAULI) - - -@ddt -class TestNumPyEigensolver(QiskitAlgorithmsTestCase): - """Test NumPy Eigen solver""" - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_ce(self, op): - """Test basics""" - algo = NumPyEigensolver() - result = algo.compute_eigenvalues(operator=op, aux_operators=[]) - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_ce_k4(self, op): - """Test for k=4 eigenvalues""" - algo = NumPyEigensolver(k=4) - result = algo.compute_eigenvalues(operator=op, aux_operators=[]) - self.assertEqual(len(result.eigenvalues), 4) - self.assertEqual(len(result.eigenstates), 4) - self.assertEqual(result.eigenvalues.dtype, np.float64) - np.testing.assert_array_almost_equal( - result.eigenvalues, [-1.85727503, -1.24458455, -0.88272215, -0.22491125] - ) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_ce_k4_filtered(self, op): - """Test for k=4 eigenvalues with filter""" - - # define filter criterion - # pylint: disable=unused-argument - def criterion(x, v, a_v): - return v >= -1 - - algo = NumPyEigensolver(k=4, filter_criterion=criterion) - result = algo.compute_eigenvalues(operator=op, aux_operators=[]) - self.assertEqual(len(result.eigenvalues), 2) - self.assertEqual(len(result.eigenstates), 2) - self.assertEqual(result.eigenvalues.dtype, np.float64) - np.testing.assert_array_almost_equal(result.eigenvalues, [-0.88272215, -0.22491125]) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_ce_k4_filtered_empty(self, op): - """Test for k=4 eigenvalues with filter always returning False""" - - # define filter criterion - # pylint: disable=unused-argument - def criterion(x, v, a_v): - return False - - algo = NumPyEigensolver(k=4, filter_criterion=criterion) - result = algo.compute_eigenvalues(operator=op, aux_operators=[]) - self.assertEqual(len(result.eigenvalues), 0) - self.assertEqual(len(result.eigenstates), 0) - - @data( - SparsePauliOp(["X"], coeffs=[1.0]), - SparsePauliOp(["Y"], coeffs=[1.0]), - SparsePauliOp(["Z"], coeffs=[1.0]), - ) - def test_ce_k1_1q(self, op): - """Test for 1 qubit operator""" - algo = NumPyEigensolver(k=1) - result = algo.compute_eigenvalues(operator=op) - np.testing.assert_array_almost_equal(result.eigenvalues, [-1]) - - @data( - SparsePauliOp(["X"], coeffs=[1.0]), - SparsePauliOp(["Y"], coeffs=[1.0]), - SparsePauliOp(["Z"], coeffs=[1.0]), - ) - def test_ce_k2_1q(self, op): - """Test for 1 qubit operator""" - algo = NumPyEigensolver(k=2) - result = algo.compute_eigenvalues(operator=op) - np.testing.assert_array_almost_equal(result.eigenvalues, [-1, 1]) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_aux_operators_list(self, op): - """Test list-based aux_operators.""" - aux_op1 = Operator(SparsePauliOp(["II"], coeffs=[2.0]).to_matrix()) - aux_op2 = SparsePauliOp(["II", "ZZ", "YY", "XX"], coeffs=[0.5, 0.5, 0.5, -0.5]) - aux_ops = [aux_op1, aux_op2] - algo = NumPyEigensolver() - result = algo.compute_eigenvalues(operator=op, aux_operators=aux_ops) - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operators_evaluated), 1) - self.assertEqual(len(result.aux_operators_evaluated[0]), 2) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[0][1][0], 0, places=6) - # metadata - self.assertAlmostEqual(result.aux_operators_evaluated[0][0][1].pop("variance"), 0.0) - self.assertAlmostEqual(result.aux_operators_evaluated[0][1][1].pop("variance"), 0.0) - - # Go again with additional None and zero operators - extra_ops = [*aux_ops, None, 0] - result = algo.compute_eigenvalues(operator=op, aux_operators=extra_ops) - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operators_evaluated), 1) - self.assertEqual(len(result.aux_operators_evaluated[0]), 4) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[0][1][0], 0, places=6) - self.assertIsNone(result.aux_operators_evaluated[0][2], None) - self.assertEqual(result.aux_operators_evaluated[0][3][0], 0.0) - # metadata - self.assertAlmostEqual(result.aux_operators_evaluated[0][0][1].pop("variance"), 0.0) - self.assertAlmostEqual(result.aux_operators_evaluated[0][1][1].pop("variance"), 0.0) - self.assertEqual(result.aux_operators_evaluated[0][3][1].pop("variance"), 0.0) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_aux_operators_dict(self, op): - """Test dict-based aux_operators.""" - aux_op1 = Operator(SparsePauliOp(["II"], coeffs=[2.0]).to_matrix()) - aux_op2 = SparsePauliOp(["II", "ZZ", "YY", "XX"], coeffs=[0.5, 0.5, 0.5, -0.5]) - aux_ops = {"aux_op1": aux_op1, "aux_op2": aux_op2} - algo = NumPyEigensolver() - result = algo.compute_eigenvalues(operator=op, aux_operators=aux_ops) - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operators_evaluated), 1) - self.assertEqual(len(result.aux_operators_evaluated[0]), 2) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op2"][0], 0, places=6) - # metadata - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op1"][1].pop("variance"), 0.0) - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op2"][1].pop("variance"), 0.0) - - # Go again with additional None and zero operators - extra_ops = {**aux_ops, "None_operator": None, "zero_operator": 0} - result = algo.compute_eigenvalues(operator=op, aux_operators=extra_ops) - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operators_evaluated), 1) - self.assertEqual(len(result.aux_operators_evaluated[0]), 3) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op2"][0], 0, places=6) - self.assertEqual(result.aux_operators_evaluated[0]["zero_operator"][0], 0.0) - self.assertTrue("None_operator" not in result.aux_operators_evaluated[0].keys()) - # metadata - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op1"][1].pop("variance"), 0.0) - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op2"][1].pop("variance"), 0.0) - self.assertAlmostEqual( - result.aux_operators_evaluated[0]["zero_operator"][1].pop("variance"), 0.0 - ) - - def test_pauli_op(self): - """Test simple pauli operator""" - algo = NumPyEigensolver(k=1) - result = algo.compute_eigenvalues(operator=Pauli("X")) - np.testing.assert_array_almost_equal(result.eigenvalues, [-1]) - - def test_scalar_op(self): - """Test scalar operator""" - algo = NumPyEigensolver(k=1) - with self.assertRaises(AlgorithmError): - algo.compute_eigenvalues(operator=ScalarOp(1)) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/eigensolvers/test_vqd.py b/test/python/algorithms/eigensolvers/test_vqd.py deleted file mode 100644 index 47eab6404f2c..000000000000 --- a/test/python/algorithms/eigensolvers/test_vqd.py +++ /dev/null @@ -1,453 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022 -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test VQD""" - -import unittest -import warnings -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -from ddt import data, ddt - -from qiskit import QuantumCircuit -from qiskit.algorithms.eigensolvers import VQD, VQDResult -from qiskit.algorithms import AlgorithmError -from qiskit.algorithms.optimizers import COBYLA, L_BFGS_B, SLSQP, SPSA -from qiskit.algorithms.state_fidelities import ComputeUncompute -from qiskit.circuit.library import TwoLocal, RealAmplitudes -from qiskit.opflow import PauliSumOp -from qiskit.primitives import Sampler, Estimator -from qiskit.quantum_info import SparsePauliOp -from qiskit.quantum_info.operators import Operator -from qiskit.utils import algorithm_globals - - -H2_SPARSE_PAULI = SparsePauliOp.from_list( - [ - ("II", -1.052373245772859), - ("IZ", 0.39793742484318045), - ("ZI", -0.39793742484318045), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423156), - ] -) -H2_OP = Operator(H2_SPARSE_PAULI.to_matrix()) - -H2_PAULI = PauliSumOp(H2_SPARSE_PAULI) - - -@ddt -class TestVQD(QiskitAlgorithmsTestCase): - """Test VQD""" - - def setUp(self): - super().setUp() - self.seed = 50 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - - self.h2_energy = -1.85727503 - self.h2_energy_excited = [-1.85727503, -1.24458455, -0.88272215, -0.22491125] - - self.ryrz_wavefunction = TwoLocal( - rotation_blocks=["ry", "rz"], entanglement_blocks="cz", reps=1 - ) - self.ry_wavefunction = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz") - - self.estimator = Estimator() - self.estimator_shots = Estimator(options={"shots": 1024, "seed": self.seed}) - self.fidelity = ComputeUncompute(Sampler()) - self.betas = [50, 50] - - @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) - def test_basic_operator(self, op): - """Test the VQD without aux_operators.""" - wavefunction = self.ryrz_wavefunction - vqd = VQD( - estimator=self.estimator, - fidelity=self.fidelity, - ansatz=wavefunction, - optimizer=COBYLA(), - betas=self.betas, - ) - - result = vqd.compute_eigenvalues(operator=op) - - with self.subTest(msg="test eigenvalue"): - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited[:2], decimal=1 - ) - - with self.subTest(msg="test dimension of optimal point"): - self.assertEqual(len(result.optimal_points[-1]), 8) - - with self.subTest(msg="assert cost_function_evals is set"): - self.assertIsNotNone(result.cost_function_evals) - - with self.subTest(msg="assert optimizer_times is set"): - self.assertIsNotNone(result.optimizer_times) - - with self.subTest(msg="assert return ansatz is set"): - job = self.estimator.run( - result.optimal_circuits, - [op] * len(result.optimal_points), - result.optimal_points, - ) - np.testing.assert_array_almost_equal(job.result().values, result.eigenvalues, 6) - - with self.subTest(msg="assert returned values are eigenvalues"): - np.testing.assert_array_almost_equal( - result.optimal_values, self.h2_energy_excited[:2], decimal=3 - ) - - def test_full_spectrum(self): - """Test obtaining all eigenvalues.""" - vqd = VQD(self.estimator, self.fidelity, self.ryrz_wavefunction, optimizer=L_BFGS_B(), k=4) - result = vqd.compute_eigenvalues(H2_PAULI) - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=2 - ) - - @data(H2_PAULI, H2_SPARSE_PAULI) - def test_beta_autoeval(self, op): - """Test beta autoevaluation for different operator types.""" - - with self.assertLogs(level="INFO") as logs: - vqd = VQD( - self.estimator_shots, self.fidelity, self.ryrz_wavefunction, optimizer=L_BFGS_B() - ) - _ = vqd.compute_eigenvalues(op) - - # the first log message shows the value of beta[0] - beta = float(logs.output[0].split()[-1]) - self.assertAlmostEqual(beta, 20.40459399499687, 4) - - @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) - def test_mismatching_num_qubits(self, op): - """Ensuring circuit and operator mismatch is caught""" - wavefunction = QuantumCircuit(1) - optimizer = SLSQP(maxiter=50) - vqd = VQD( - estimator=self.estimator, - fidelity=self.fidelity, - k=1, - ansatz=wavefunction, - optimizer=optimizer, - betas=self.betas, - ) - with self.assertRaises(AlgorithmError): - _ = vqd.compute_eigenvalues(operator=op) - - @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) - def test_missing_varform_params(self, op): - """Test specifying a variational form with no parameters raises an error.""" - circuit = QuantumCircuit(op.num_qubits) - vqd = VQD( - estimator=self.estimator, - fidelity=self.fidelity, - ansatz=circuit, - optimizer=SLSQP(), - k=1, - betas=self.betas, - ) - with self.assertRaises(AlgorithmError): - vqd.compute_eigenvalues(operator=op) - - @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) - def test_callback(self, op): - """Test the callback on VQD.""" - history = {"eval_count": [], "parameters": [], "mean": [], "metadata": [], "step": []} - - def store_intermediate_result(eval_count, parameters, mean, metadata, step): - history["eval_count"].append(eval_count) - history["parameters"].append(parameters) - history["mean"].append(mean) - history["metadata"].append(metadata) - history["step"].append(step) - - optimizer = COBYLA(maxiter=3) - wavefunction = self.ry_wavefunction - - vqd = VQD( - estimator=self.estimator_shots, - fidelity=self.fidelity, - ansatz=wavefunction, - optimizer=optimizer, - callback=store_intermediate_result, - betas=self.betas, - ) - - vqd.compute_eigenvalues(operator=op) - - self.assertTrue(all(isinstance(count, int) for count in history["eval_count"])) - self.assertTrue(all(isinstance(mean, float) for mean in history["mean"])) - self.assertTrue(all(isinstance(metadata, dict) for metadata in history["metadata"])) - self.assertTrue(all(isinstance(count, int) for count in history["step"])) - for params in history["parameters"]: - self.assertTrue(all(isinstance(param, float) for param in params)) - - ref_eval_count = [1, 2, 3, 1, 2, 3] - ref_mean = [-1.07, -1.45, -1.37, 37.43, 48.55, 28.94] - # new ref_mean for statevector simulator. The old unit test was on qasm - # and the ref_mean values were slightly different. - - ref_step = [1, 1, 1, 2, 2, 2] - - np.testing.assert_array_almost_equal(history["eval_count"], ref_eval_count, decimal=0) - np.testing.assert_array_almost_equal(history["mean"], ref_mean, decimal=2) - np.testing.assert_array_almost_equal(history["step"], ref_step, decimal=0) - - @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) - def test_vqd_optimizer(self, op): - """Test running same VQD twice to re-use optimizer, then switch optimizer""" - - vqd = VQD( - estimator=self.estimator, - fidelity=self.fidelity, - ansatz=RealAmplitudes(), - optimizer=SLSQP(), - k=2, - betas=self.betas, - ) - - def run_check(): - result = vqd.compute_eigenvalues(operator=op) - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited[:2], decimal=3 - ) - - run_check() - - with self.subTest("Optimizer re-use"): - run_check() - - with self.subTest("Optimizer replace"): - vqd.optimizer = L_BFGS_B() - run_check() - - with self.subTest("Batched optimizer replace"): - vqd.optimizer = SLSQP(maxiter=60, max_evals_grouped=10) - run_check() - - with self.subTest("SPSA replace"): - # SPSA takes too long to converge, so we will - # only check that it runs with no errors. - vqd.optimizer = SPSA(maxiter=5, learning_rate=0.01, perturbation=0.01) - result = vqd.compute_eigenvalues(operator=op) - self.assertIsInstance(result, VQDResult) - - @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) - def test_optimizer_list(self, op): - """Test sending an optimizer list""" - - optimizers = [SLSQP(), L_BFGS_B()] - initial_point_1 = [ - 1.70256666, - -5.34843975, - -0.39542903, - 5.99477786, - -2.74374986, - -4.85284669, - 0.2442925, - -1.51638917, - ] - initial_point_2 = [ - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - ] - vqd = VQD( - estimator=self.estimator, - fidelity=self.fidelity, - ansatz=RealAmplitudes(), - optimizer=optimizers, - initial_point=[initial_point_1, initial_point_2], - k=2, - betas=self.betas, - ) - - result = vqd.compute_eigenvalues(operator=op) - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited[:2], decimal=3 - ) - - @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) - def test_aux_operators_list(self, op): - """Test list-based aux_operators.""" - wavefunction = self.ry_wavefunction - vqd = VQD( - estimator=self.estimator, - fidelity=self.fidelity, - ansatz=wavefunction, - optimizer=SLSQP(), - k=2, - betas=self.betas, - ) - - # Start with an empty list - result = vqd.compute_eigenvalues(op, aux_operators=[]) - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited[:2], decimal=2 - ) - self.assertIsNone(result.aux_operators_evaluated) - - # Go again with two auxiliary operators - aux_op1 = SparsePauliOp.from_list([("II", 2.0)]) - aux_op2 = SparsePauliOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - result = vqd.compute_eigenvalues(op, aux_operators=aux_ops) - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited[:2], decimal=2 - ) - self.assertEqual(len(result.aux_operators_evaluated), 2) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0][0], 2, places=2) - self.assertAlmostEqual(result.aux_operators_evaluated[0][1][0], 0, places=2) - # metadata - self.assertIsInstance(result.aux_operators_evaluated[0][1][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0][1][1], dict) - - # Go again with additional None and zero operators - extra_ops = [*aux_ops, None, 0] - result = vqd.compute_eigenvalues(op, aux_operators=extra_ops) - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited[:2], decimal=2 - ) - self.assertEqual(len(result.aux_operators_evaluated), 2) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0][0], 2, places=2) - self.assertAlmostEqual(result.aux_operators_evaluated[0][1][0], 0, places=2) - self.assertEqual(result.aux_operators_evaluated[0][2][0], 0.0) - self.assertEqual(result.aux_operators_evaluated[0][3][0], 0.0) - # metadata - self.assertIsInstance(result.aux_operators_evaluated[0][0][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0][1][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0][3][1], dict) - - @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) - def test_aux_operators_dict(self, op): - """Test dictionary compatibility of aux_operators""" - wavefunction = self.ry_wavefunction - vqd = VQD( - estimator=self.estimator, - fidelity=self.fidelity, - ansatz=wavefunction, - optimizer=SLSQP(), - betas=self.betas, - ) - - # Start with an empty dictionary - result = vqd.compute_eigenvalues(op, aux_operators={}) - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited[:2], decimal=2 - ) - self.assertIsNone(result.aux_operators_evaluated) - - # Go again with two auxiliary operators - aux_op1 = SparsePauliOp.from_list([("II", 2.0)]) - aux_op2 = SparsePauliOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = {"aux_op1": aux_op1, "aux_op2": aux_op2} - result = vqd.compute_eigenvalues(op, aux_operators=aux_ops) - self.assertEqual(len(result.eigenvalues), 2) - self.assertEqual(result.eigenvalues.dtype, np.complex128) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503, 2) - self.assertEqual(len(result.aux_operators_evaluated), 2) - self.assertEqual(len(result.aux_operators_evaluated[0]), 2) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op2"][0], 0, places=1) - # metadata - self.assertIsInstance(result.aux_operators_evaluated[0]["aux_op1"][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0]["aux_op2"][1], dict) - - # Go again with additional None and zero operators - extra_ops = {**aux_ops, "None_operator": None, "zero_operator": 0} - result = vqd.compute_eigenvalues(op, aux_operators=extra_ops) - self.assertEqual(len(result.eigenvalues), 2) - self.assertEqual(result.eigenvalues.dtype, np.complex128) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503, places=5) - self.assertEqual(len(result.aux_operators_evaluated), 2) - self.assertEqual(len(result.aux_operators_evaluated[0]), 3) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[0]["aux_op2"][0], 0.0, places=2) - self.assertEqual(result.aux_operators_evaluated[0]["zero_operator"][0], 0.0) - self.assertTrue("None_operator" not in result.aux_operators_evaluated[0].keys()) - # metadata - self.assertIsInstance(result.aux_operators_evaluated[0]["aux_op1"][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0]["aux_op2"][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0]["zero_operator"][1], dict) - - @data(H2_PAULI, H2_OP, H2_SPARSE_PAULI) - def test_aux_operator_std_dev(self, op): - """Test non-zero standard deviations of aux operators.""" - wavefunction = self.ry_wavefunction - vqd = VQD( - estimator=self.estimator, - fidelity=self.fidelity, - ansatz=wavefunction, - initial_point=[ - 1.70256666, - -5.34843975, - -0.39542903, - 5.99477786, - -2.74374986, - -4.85284669, - 0.2442925, - -1.51638917, - ], - optimizer=COBYLA(maxiter=0), - betas=self.betas, - ) - - # Go again with two auxiliary operators - aux_op1 = SparsePauliOp.from_list([("II", 2.0)]) - aux_op2 = SparsePauliOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - result = vqd.compute_eigenvalues(op, aux_operators=aux_ops) - self.assertEqual(len(result.aux_operators_evaluated), 2) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0][0], 2.0, places=1) - self.assertAlmostEqual( - result.aux_operators_evaluated[0][1][0], 0.0019531249999999445, places=1 - ) - # metadata - self.assertIsInstance(result.aux_operators_evaluated[0][0][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0][1][1], dict) - - # Go again with additional None and zero operators - aux_ops = [*aux_ops, None, 0] - result = vqd.compute_eigenvalues(op, aux_operators=aux_ops) - self.assertEqual(len(result.aux_operators_evaluated[0]), 4) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0][0], 2.0, places=1) - self.assertAlmostEqual( - result.aux_operators_evaluated[0][1][0], 0.0019531249999999445, places=1 - ) - self.assertEqual(result.aux_operators_evaluated[0][2][0], 0.0) - self.assertEqual(result.aux_operators_evaluated[0][3][0], 0.0) - # metadata - self.assertIsInstance(result.aux_operators_evaluated[0][0][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0][1][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0][2][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[0][3][1], dict) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/evolvers/__init__.py b/test/python/algorithms/evolvers/__init__.py deleted file mode 100644 index fdb172d367f0..000000000000 --- a/test/python/algorithms/evolvers/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. diff --git a/test/python/algorithms/evolvers/test_evolution_problem.py b/test/python/algorithms/evolvers/test_evolution_problem.py deleted file mode 100644 index dc9d45f548a2..000000000000 --- a/test/python/algorithms/evolvers/test_evolution_problem.py +++ /dev/null @@ -1,136 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test evolver problem class.""" -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import data, ddt, unpack -from numpy.testing import assert_raises - -from qiskit.algorithms.evolvers.evolution_problem import EvolutionProblem -from qiskit.circuit import Parameter -from qiskit.opflow import Y, Z, One, X, Zero - - -@ddt -class TestEvolutionProblem(QiskitAlgorithmsTestCase): - """Test evolver problem class.""" - - def test_init_default(self): - """Tests that all default fields are initialized correctly.""" - hamiltonian = Y - time = 2.5 - initial_state = One - - with self.assertWarns(DeprecationWarning): - evo_problem = EvolutionProblem(hamiltonian, time, initial_state) - - expected_hamiltonian = Y - expected_time = 2.5 - expected_initial_state = One - expected_aux_operators = None - expected_t_param = None - expected_param_value_dict = None - - self.assertEqual(evo_problem.hamiltonian, expected_hamiltonian) - self.assertEqual(evo_problem.time, expected_time) - self.assertEqual(evo_problem.initial_state, expected_initial_state) - self.assertEqual(evo_problem.aux_operators, expected_aux_operators) - self.assertEqual(evo_problem.t_param, expected_t_param) - self.assertEqual(evo_problem.param_value_dict, expected_param_value_dict) - - def test_init_all(self): - """Tests that all fields are initialized correctly.""" - t_parameter = Parameter("t") - with self.assertWarns(DeprecationWarning): - hamiltonian = t_parameter * Z + Y - time = 2 - initial_state = One - aux_operators = [X, Y] - param_value_dict = {t_parameter: 3.2} - - with self.assertWarns(DeprecationWarning): - evo_problem = EvolutionProblem( - hamiltonian, - time, - initial_state, - aux_operators, - t_param=t_parameter, - param_value_dict=param_value_dict, - ) - expected_hamiltonian = Y + t_parameter * Z - - expected_time = 2 - expected_initial_state = One - expected_aux_operators = [X, Y] - expected_t_param = t_parameter - expected_param_value_dict = {t_parameter: 3.2} - - with self.assertWarns(DeprecationWarning): - self.assertEqual(evo_problem.hamiltonian, expected_hamiltonian) - - self.assertEqual(evo_problem.time, expected_time) - self.assertEqual(evo_problem.initial_state, expected_initial_state) - self.assertEqual(evo_problem.aux_operators, expected_aux_operators) - self.assertEqual(evo_problem.t_param, expected_t_param) - self.assertEqual(evo_problem.param_value_dict, expected_param_value_dict) - - @data([Y, -1, One], [Y, -1.2, One], [Y, 0, One]) - @unpack - def test_init_errors(self, hamiltonian, time, initial_state): - """Tests expected errors are thrown on invalid time argument.""" - with self.assertWarns(DeprecationWarning), assert_raises(ValueError): - _ = EvolutionProblem(hamiltonian, time, initial_state) - - def test_validate_params(self): - """Tests expected errors are thrown on parameters mismatch.""" - param_x = Parameter("x") - param_y = Parameter("y") - with self.subTest(msg="Parameter missing in dict."): - with self.assertWarns(DeprecationWarning): - hamiltonian = param_x * X + param_y * Y - - param_dict = {param_y: 2} - with self.assertWarns(DeprecationWarning): - evolution_problem = EvolutionProblem( - hamiltonian, 2, Zero, param_value_dict=param_dict - ) - with assert_raises(ValueError): - evolution_problem.validate_params() - - with self.subTest(msg="Empty dict."): - with self.assertWarns(DeprecationWarning): - hamiltonian = param_x * X + param_y * Y - - param_dict = {} - with self.assertWarns(DeprecationWarning): - evolution_problem = EvolutionProblem( - hamiltonian, 2, Zero, param_value_dict=param_dict - ) - with assert_raises(ValueError): - evolution_problem.validate_params() - - with self.subTest(msg="Extra parameter in dict."): - with self.assertWarns(DeprecationWarning): - hamiltonian = param_x * X + param_y * Y - - param_dict = {param_y: 2, param_x: 1, Parameter("z"): 1} - with self.assertWarns(DeprecationWarning): - evolution_problem = EvolutionProblem( - hamiltonian, 2, Zero, param_value_dict=param_dict - ) - with assert_raises(ValueError): - evolution_problem.validate_params() - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/evolvers/test_evolution_result.py b/test/python/algorithms/evolvers/test_evolution_result.py deleted file mode 100644 index 2fe40d8c66f8..000000000000 --- a/test/python/algorithms/evolvers/test_evolution_result.py +++ /dev/null @@ -1,50 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""Class for testing evolution result.""" -import unittest - -from test.python.algorithms import QiskitAlgorithmsTestCase -from qiskit.algorithms.evolvers.evolution_result import EvolutionResult -from qiskit.opflow import Zero - - -class TestEvolutionResult(QiskitAlgorithmsTestCase): - """Class for testing evolution result and relevant metadata.""" - - def test_init_state(self): - """Tests that a class is initialized correctly with an evolved_state.""" - evolved_state = Zero - with self.assertWarns(DeprecationWarning): - evo_result = EvolutionResult(evolved_state=evolved_state) - - expected_state = Zero - expected_aux_ops_evaluated = None - - self.assertEqual(evo_result.evolved_state, expected_state) - self.assertEqual(evo_result.aux_ops_evaluated, expected_aux_ops_evaluated) - - def test_init_observable(self): - """Tests that a class is initialized correctly with an evolved_observable.""" - evolved_state = Zero - evolved_aux_ops_evaluated = [(5j, 5j), (1.0, 8j), (5 + 1j, 6 + 1j)] - with self.assertWarns(DeprecationWarning): - evo_result = EvolutionResult(evolved_state, evolved_aux_ops_evaluated) - - expected_state = Zero - expected_aux_ops_evaluated = [(5j, 5j), (1.0, 8j), (5 + 1j, 6 + 1j)] - - self.assertEqual(evo_result.evolved_state, expected_state) - self.assertEqual(evo_result.aux_ops_evaluated, expected_aux_ops_evaluated) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/evolvers/trotterization/__init__.py b/test/python/algorithms/evolvers/trotterization/__init__.py deleted file mode 100644 index 96c0cf22bec9..000000000000 --- a/test/python/algorithms/evolvers/trotterization/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. diff --git a/test/python/algorithms/evolvers/trotterization/test_trotter_qrte.py b/test/python/algorithms/evolvers/trotterization/test_trotter_qrte.py deleted file mode 100644 index 6635d4db5d85..000000000000 --- a/test/python/algorithms/evolvers/trotterization/test_trotter_qrte.py +++ /dev/null @@ -1,259 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test TrotterQRTE.""" - -import unittest -import warnings - -from test.python.opflow import QiskitOpflowTestCase -from ddt import ddt, data, unpack -import numpy as np -from numpy.testing import assert_raises - -from qiskit import BasicAer, QuantumCircuit -from qiskit.algorithms import EvolutionProblem -from qiskit.algorithms.evolvers.trotterization import ( - TrotterQRTE, -) -from qiskit.circuit.library import ZGate -from qiskit.quantum_info import Statevector -from qiskit.utils import algorithm_globals, QuantumInstance -from qiskit.circuit import Parameter -from qiskit.opflow import ( - X, - Z, - Zero, - VectorStateFn, - StateFn, - I, - Y, - SummedOp, - ExpectationFactory, -) -from qiskit.synthesis import SuzukiTrotter, QDrift - - -@ddt -class TestTrotterQRTE(QiskitOpflowTestCase): - """TrotterQRTE tests.""" - - def setUp(self): - super().setUp() - self.seed = 50 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - backend_statevector = BasicAer.get_backend("statevector_simulator") - backend_qasm = BasicAer.get_backend("qasm_simulator") - with self.assertWarns(DeprecationWarning): - self.quantum_instance = QuantumInstance( - backend=backend_statevector, - shots=1, - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - self.quantum_instance_qasm = QuantumInstance( - backend=backend_qasm, - shots=8000, - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - self.backends_dict = { - "qi_sv": self.quantum_instance, - "qi_qasm": self.quantum_instance_qasm, - "b_sv": backend_statevector, - "None": None, - } - - self.backends_names = ["qi_qasm", "b_sv", "None", "qi_sv"] - self.backends_names_not_none = ["qi_sv", "b_sv", "qi_qasm"] - - @data( - ( - None, - VectorStateFn( - Statevector([0.29192658 - 0.45464871j, 0.70807342 - 0.45464871j], dims=(2,)) - ), - ), - ( - SuzukiTrotter(), - VectorStateFn(Statevector([0.29192658 - 0.84147098j, 0.0 - 0.45464871j], dims=(2,))), - ), - ) - @unpack - def test_trotter_qrte_trotter_single_qubit(self, product_formula, expected_state): - """Test for default TrotterQRTE on a single qubit.""" - operator = SummedOp([X, Z]) - initial_state = StateFn([1, 0]) - time = 1 - with self.assertWarns(DeprecationWarning): - evolution_problem = EvolutionProblem(operator, time, initial_state) - trotter_qrte = TrotterQRTE(product_formula=product_formula) - evolution_result_state_circuit = trotter_qrte.evolve(evolution_problem).evolved_state - - np.testing.assert_equal(evolution_result_state_circuit.eval(), expected_state) - - def test_trotter_qrte_trotter_single_qubit_aux_ops(self): - """Test for default TrotterQRTE on a single qubit with auxiliary operators.""" - operator = SummedOp([X, Z]) - # LieTrotter with 1 rep - aux_ops = [X, Y] - - initial_state = Zero - time = 3 - with self.assertWarns(DeprecationWarning): - evolution_problem = EvolutionProblem(operator, time, initial_state, aux_ops) - - expected_evolved_state = VectorStateFn( - Statevector([0.98008514 + 0.13970775j, 0.01991486 + 0.13970775j], dims=(2,)) - ) - expected_aux_ops_evaluated = [(0.078073, 0.0), (0.268286, 0.0)] - expected_aux_ops_evaluated_qasm = [ - (0.05799999999999995, 0.011161518713866855), - (0.2495, 0.010826759383582883), - ] - - for backend_name in self.backends_names_not_none: - with self.subTest(msg=f"Test {backend_name} backend."): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - backend = self.backends_dict[backend_name] - with self.assertWarns(DeprecationWarning): - expectation = ExpectationFactory.build( - operator=operator, - backend=backend, - ) - with self.assertWarns(DeprecationWarning): - trotter_qrte = TrotterQRTE(quantum_instance=backend, expectation=expectation) - evolution_result = trotter_qrte.evolve(evolution_problem) - - np.testing.assert_equal( - evolution_result.evolved_state.eval(), expected_evolved_state - ) - if backend_name == "qi_qasm": - expected_aux_ops_evaluated = expected_aux_ops_evaluated_qasm - np.testing.assert_array_almost_equal( - evolution_result.aux_ops_evaluated, expected_aux_ops_evaluated - ) - - @data( - ( - SummedOp([(X ^ Y), (Y ^ X)]), - VectorStateFn( - Statevector( - [-0.41614684 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.90929743 + 0.0j], dims=(2, 2) - ) - ), - ), - ( - (Z ^ Z) + (Z ^ I) + (I ^ Z), - VectorStateFn( - Statevector( - [-0.9899925 - 0.14112001j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j], dims=(2, 2) - ) - ), - ), - ( - Y ^ Y, - VectorStateFn( - Statevector( - [0.54030231 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.84147098j], dims=(2, 2) - ) - ), - ), - ) - @unpack - def test_trotter_qrte_trotter_two_qubits(self, operator, expected_state): - """Test for TrotterQRTE on two qubits with various types of a Hamiltonian.""" - # LieTrotter with 1 rep - initial_state = StateFn([1, 0, 0, 0]) - with self.assertWarns(DeprecationWarning): - evolution_problem = EvolutionProblem(operator, 1, initial_state) - trotter_qrte = TrotterQRTE() - evolution_result = trotter_qrte.evolve(evolution_problem) - np.testing.assert_equal(evolution_result.evolved_state.eval(), expected_state) - - def test_trotter_qrte_trotter_two_qubits_with_params(self): - """Test for TrotterQRTE on two qubits with a parametrized Hamiltonian.""" - # LieTrotter with 1 rep - initial_state = StateFn([1, 0, 0, 0]) - w_param = Parameter("w") - u_param = Parameter("u") - params_dict = {w_param: 2.0, u_param: 3.0} - operator = w_param * (Z ^ Z) / 2.0 + (Z ^ I) + u_param * (I ^ Z) / 3.0 - time = 1 - expected_state = VectorStateFn( - Statevector([-0.9899925 - 0.14112001j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j], dims=(2, 2)) - ) - with self.assertWarns(DeprecationWarning): - evolution_problem = EvolutionProblem( - operator, time, initial_state, param_value_dict=params_dict - ) - trotter_qrte = TrotterQRTE() - evolution_result = trotter_qrte.evolve(evolution_problem) - np.testing.assert_equal(evolution_result.evolved_state.eval(), expected_state) - - @data( - ( - Zero, - VectorStateFn( - Statevector([0.23071786 - 0.69436148j, 0.4646314 - 0.49874749j], dims=(2,)) - ), - ), - ( - QuantumCircuit(1).compose(ZGate(), [0]), - VectorStateFn( - Statevector([0.23071786 - 0.69436148j, 0.4646314 - 0.49874749j], dims=(2,)) - ), - ), - ) - @unpack - def test_trotter_qrte_qdrift(self, initial_state, expected_state): - """Test for TrotterQRTE with QDrift.""" - operator = SummedOp([X, Z]) - time = 1 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - with self.assertWarns(DeprecationWarning): - evolution_problem = EvolutionProblem(operator, time, initial_state) - trotter_qrte = TrotterQRTE(product_formula=QDrift(seed=0)) - evolution_result = trotter_qrte.evolve(evolution_problem) - np.testing.assert_equal(evolution_result.evolved_state.eval(), expected_state) - - @data((Parameter("t"), {}), (None, {Parameter("x"): 2}), (None, None)) - @unpack - def test_trotter_qrte_trotter_errors(self, t_param, param_value_dict): - """Test TrotterQRTE with raising errors.""" - operator = X * Parameter("t") + Z - initial_state = Zero - time = 1 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - with self.assertWarns(DeprecationWarning): - trotter_qrte = TrotterQRTE() - evolution_problem = EvolutionProblem( - operator, - time, - initial_state, - t_param=t_param, - param_value_dict=param_value_dict, - ) - with assert_raises(ValueError): - _ = trotter_qrte.evolve(evolution_problem) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/gradients/__init__.py b/test/python/algorithms/gradients/__init__.py deleted file mode 100644 index 756b2a26196b..000000000000 --- a/test/python/algorithms/gradients/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Tests for the primitive-based gradients""" diff --git a/test/python/algorithms/gradients/logging_primitives.py b/test/python/algorithms/gradients/logging_primitives.py deleted file mode 100644 index ef2cad9e2cc0..000000000000 --- a/test/python/algorithms/gradients/logging_primitives.py +++ /dev/null @@ -1,42 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test primitives that check what kind of operations are in the circuits they execute.""" - -from qiskit.primitives import Estimator, Sampler - - -class LoggingEstimator(Estimator): - """An estimator checking what operations were in the circuits it executed.""" - - def __init__(self, options=None, operations_callback=None): - super().__init__(options=options) - self.operations_callback = operations_callback - - def _run(self, circuits, observables, parameter_values, **run_options): - if self.operations_callback is not None: - ops = [circuit.count_ops() for circuit in circuits] - self.operations_callback(ops) - return super()._run(circuits, observables, parameter_values, **run_options) - - -class LoggingSampler(Sampler): - """A sampler checking what operations were in the circuits it executed.""" - - def __init__(self, operations_callback): - super().__init__() - self.operations_callback = operations_callback - - def _run(self, circuits, parameter_values, **run_options): - ops = [circuit.count_ops() for circuit in circuits] - self.operations_callback(ops) - return super()._run(circuits, parameter_values, **run_options) diff --git a/test/python/algorithms/gradients/test_estimator_gradient.py b/test/python/algorithms/gradients/test_estimator_gradient.py deleted file mode 100644 index 202727c5835a..000000000000 --- a/test/python/algorithms/gradients/test_estimator_gradient.py +++ /dev/null @@ -1,519 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -# ============================================================================= - -"""Test Estimator Gradients""" - -import unittest - -import numpy as np -from ddt import ddt, data, unpack - -from qiskit import QuantumCircuit -from qiskit.algorithms.gradients import ( - FiniteDiffEstimatorGradient, - LinCombEstimatorGradient, - ParamShiftEstimatorGradient, - SPSAEstimatorGradient, - ReverseEstimatorGradient, - DerivativeType, -) -from qiskit.circuit import Parameter -from qiskit.circuit.library import EfficientSU2, RealAmplitudes -from qiskit.circuit.library.standard_gates import RXXGate, RYYGate, RZXGate, RZZGate -from qiskit.primitives import Estimator -from qiskit.quantum_info import Operator, SparsePauliOp, Pauli -from qiskit.quantum_info.random import random_pauli_list -from qiskit.test import QiskitTestCase - -from .logging_primitives import LoggingEstimator - -gradient_factories = [ - lambda estimator: FiniteDiffEstimatorGradient(estimator, epsilon=1e-6, method="central"), - lambda estimator: FiniteDiffEstimatorGradient(estimator, epsilon=1e-6, method="forward"), - lambda estimator: FiniteDiffEstimatorGradient(estimator, epsilon=1e-6, method="backward"), - ParamShiftEstimatorGradient, - LinCombEstimatorGradient, - lambda estimator: ReverseEstimatorGradient(), # does not take an estimator! -] - - -@ddt -class TestEstimatorGradient(QiskitTestCase): - """Test Estimator Gradient""" - - @data(*gradient_factories) - def test_gradient_operators(self, grad): - """Test the estimator gradient for different operators""" - estimator = Estimator() - a = Parameter("a") - qc = QuantumCircuit(1) - qc.h(0) - qc.p(a, 0) - qc.h(0) - gradient = grad(estimator) - op = SparsePauliOp.from_list([("Z", 1)]) - correct_result = -1 / np.sqrt(2) - param = [np.pi / 4] - value = gradient.run([qc], [op], [param]).result().gradients[0] - self.assertAlmostEqual(value[0], correct_result, 3) - op = SparsePauliOp.from_list([("Z", 1)]) - value = gradient.run([qc], [op], [param]).result().gradients[0] - self.assertAlmostEqual(value[0], correct_result, 3) - op = Operator.from_label("Z") - value = gradient.run([qc], [op], [param]).result().gradients[0] - self.assertAlmostEqual(value[0], correct_result, 3) - - @data(*gradient_factories) - def test_single_circuit_observable(self, grad): - """Test the estimator gradient for a single circuit and observable""" - estimator = Estimator() - a = Parameter("a") - qc = QuantumCircuit(1) - qc.h(0) - qc.p(a, 0) - qc.h(0) - gradient = grad(estimator) - op = SparsePauliOp.from_list([("Z", 1)]) - correct_result = -1 / np.sqrt(2) - param = [np.pi / 4] - value = gradient.run(qc, op, [param]).result().gradients[0] - self.assertAlmostEqual(value[0], correct_result, 3) - - @data(*gradient_factories) - def test_gradient_p(self, grad): - """Test the estimator gradient for p""" - estimator = Estimator() - a = Parameter("a") - qc = QuantumCircuit(1) - qc.h(0) - qc.p(a, 0) - qc.h(0) - gradient = grad(estimator) - op = SparsePauliOp.from_list([("Z", 1)]) - param_list = [[np.pi / 4], [0], [np.pi / 2]] - correct_results = [[-1 / np.sqrt(2)], [0], [-1]] - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [op], [param]).result().gradients[0] - for j, value in enumerate(gradients): - self.assertAlmostEqual(value, correct_results[i][j], 3) - - @data(*gradient_factories) - def test_gradient_u(self, grad): - """Test the estimator gradient for u""" - estimator = Estimator() - a = Parameter("a") - b = Parameter("b") - c = Parameter("c") - qc = QuantumCircuit(1) - qc.h(0) - qc.u(a, b, c, 0) - qc.h(0) - gradient = grad(estimator) - op = SparsePauliOp.from_list([("Z", 1)]) - - param_list = [[np.pi / 4, 0, 0], [np.pi / 4, np.pi / 4, np.pi / 4]] - correct_results = [[-0.70710678, 0.0, 0.0], [-0.35355339, -0.85355339, -0.85355339]] - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [op], [param]).result().gradients[0] - for j, value in enumerate(gradients): - self.assertAlmostEqual(value, correct_results[i][j], 3) - - @data(*gradient_factories) - def test_gradient_efficient_su2(self, grad): - """Test the estimator gradient for EfficientSU2""" - estimator = Estimator() - qc = EfficientSU2(2, reps=1) - op = SparsePauliOp.from_list([("ZI", 1)]) - gradient = grad(estimator) - param_list = [ - [np.pi / 4 for param in qc.parameters], - [np.pi / 2 for param in qc.parameters], - ] - correct_results = [ - [ - -0.35355339, - -0.70710678, - 0, - 0.35355339, - 0, - -0.70710678, - 0, - 0, - ], - [0, 0, 0, 1, 0, 0, 0, 0], - ] - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [op], [param]).result().gradients[0] - np.testing.assert_allclose(gradients, correct_results[i], atol=1e-3) - - @data(*gradient_factories) - def test_gradient_2qubit_gate(self, grad): - """Test the estimator gradient for 2 qubit gates""" - estimator = Estimator() - for gate in [RXXGate, RYYGate, RZZGate, RZXGate]: - param_list = [[np.pi / 4], [np.pi / 2]] - correct_results = [ - [-0.70710678], - [-1], - ] - op = SparsePauliOp.from_list([("ZI", 1)]) - for i, param in enumerate(param_list): - a = Parameter("a") - qc = QuantumCircuit(2) - gradient = grad(estimator) - - if gate is RZZGate: - qc.h([0, 1]) - qc.append(gate(a), [qc.qubits[0], qc.qubits[1]], []) - qc.h([0, 1]) - else: - qc.append(gate(a), [qc.qubits[0], qc.qubits[1]], []) - gradients = gradient.run([qc], [op], [param]).result().gradients[0] - np.testing.assert_allclose(gradients, correct_results[i], atol=1e-3) - - @data(*gradient_factories) - def test_gradient_parameter_coefficient(self, grad): - """Test the estimator gradient for parameter variables with coefficients""" - estimator = Estimator() - qc = RealAmplitudes(num_qubits=2, reps=1) - qc.rz(qc.parameters[0].exp() + 2 * qc.parameters[1], 0) - qc.rx(3.0 * qc.parameters[0] + qc.parameters[1].sin(), 1) - qc.u(qc.parameters[0], qc.parameters[1], qc.parameters[3], 1) - qc.p(2 * qc.parameters[0] + 1, 0) - qc.rxx(qc.parameters[0] + 2, 0, 1) - gradient = grad(estimator) - param_list = [[np.pi / 4 for _ in qc.parameters], [np.pi / 2 for _ in qc.parameters]] - correct_results = [ - [-0.7266653, -0.4905135, -0.0068606, -0.9228880], - [-3.5972095, 0.10237173, -0.3117748, 0], - ] - op = SparsePauliOp.from_list([("ZI", 1)]) - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [op], [param]).result().gradients[0] - np.testing.assert_allclose(gradients, correct_results[i], atol=1e-3) - - @data(*gradient_factories) - def test_gradient_parameters(self, grad): - """Test the estimator gradient for parameters""" - estimator = Estimator() - a = Parameter("a") - b = Parameter("b") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.rx(b, 0) - gradient = grad(estimator) - param_list = [[np.pi / 4, np.pi / 2]] - correct_results = [ - [-0.70710678], - ] - op = SparsePauliOp.from_list([("Z", 1)]) - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [op], [param], parameters=[[a]]).result().gradients[0] - np.testing.assert_allclose(gradients, correct_results[i], atol=1e-3) - - # parameter order - with self.subTest(msg="The order of gradients"): - c = Parameter("c") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.rz(b, 0) - qc.rx(c, 0) - - param_list = [[np.pi / 4, np.pi / 2, np.pi / 3]] - correct_results = [ - [-0.35355339, 0.61237244, -0.61237244], - [-0.61237244, 0.61237244, -0.35355339], - [-0.35355339, -0.61237244], - [-0.61237244, -0.35355339], - ] - param = [[a, b, c], [c, b, a], [a, c], [c, a]] - op = SparsePauliOp.from_list([("Z", 1)]) - for i, p in enumerate(param): - gradient = grad(estimator) - gradients = ( - gradient.run([qc], [op], param_list, parameters=[p]).result().gradients[0] - ) - np.testing.assert_allclose(gradients, correct_results[i], atol=1e-3) - - @data(*gradient_factories) - def test_gradient_multi_arguments(self, grad): - """Test the estimator gradient for multiple arguments""" - estimator = Estimator() - a = Parameter("a") - b = Parameter("b") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc2 = QuantumCircuit(1) - qc2.rx(b, 0) - gradient = grad(estimator) - param_list = [[np.pi / 4], [np.pi / 2]] - correct_results = [ - [-0.70710678], - [-1], - ] - op = SparsePauliOp.from_list([("Z", 1)]) - gradients = gradient.run([qc, qc2], [op] * 2, param_list).result().gradients - np.testing.assert_allclose(gradients, correct_results, atol=1e-3) - - c = Parameter("c") - qc3 = QuantumCircuit(1) - qc3.rx(c, 0) - qc3.ry(a, 0) - param_list2 = [[np.pi / 4], [np.pi / 4, np.pi / 4], [np.pi / 4, np.pi / 4]] - correct_results2 = [ - [-0.70710678], - [-0.5], - [-0.5, -0.5], - ] - gradients2 = ( - gradient.run([qc, qc3, qc3], [op] * 3, param_list2, parameters=[[a], [c], None]) - .result() - .gradients - ) - np.testing.assert_allclose(gradients2[0], correct_results2[0], atol=1e-3) - np.testing.assert_allclose(gradients2[1], correct_results2[1], atol=1e-3) - np.testing.assert_allclose(gradients2[2], correct_results2[2], atol=1e-3) - - @data(*gradient_factories) - def test_gradient_validation(self, grad): - """Test estimator gradient's validation""" - estimator = Estimator() - a = Parameter("a") - qc = QuantumCircuit(1) - qc.rx(a, 0) - gradient = grad(estimator) - param_list = [[np.pi / 4], [np.pi / 2]] - op = SparsePauliOp.from_list([("Z", 1)]) - with self.assertRaises(ValueError): - gradient.run([qc], [op], param_list) - with self.assertRaises(ValueError): - gradient.run([qc, qc], [op, op], param_list, parameters=[[a]]) - with self.assertRaises(ValueError): - gradient.run([qc, qc], [op], param_list, parameters=[[a]]) - with self.assertRaises(ValueError): - gradient.run([qc], [op], [[np.pi / 4, np.pi / 4]]) - - def test_spsa_gradient(self): - """Test the SPSA estimator gradient""" - estimator = Estimator() - with self.assertRaises(ValueError): - _ = SPSAEstimatorGradient(estimator, epsilon=-0.1) - a = Parameter("a") - b = Parameter("b") - qc = QuantumCircuit(2) - qc.rx(b, 0) - qc.rx(a, 1) - param_list = [[1, 1]] - correct_results = [[-0.84147098, 0.84147098]] - op = SparsePauliOp.from_list([("ZI", 1)]) - gradient = SPSAEstimatorGradient(estimator, epsilon=1e-6, seed=123) - gradients = gradient.run([qc], [op], param_list).result().gradients - np.testing.assert_allclose(gradients, correct_results, atol=1e-3) - - # multi parameters - with self.subTest(msg="Multiple parameters"): - gradient = SPSAEstimatorGradient(estimator, epsilon=1e-6, seed=123) - param_list2 = [[1, 1], [1, 1], [3, 3]] - gradients2 = ( - gradient.run([qc] * 3, [op] * 3, param_list2, parameters=[None, [b], None]) - .result() - .gradients - ) - correct_results2 = [[-0.84147098, 0.84147098], [0.84147098], [-0.14112001, 0.14112001]] - for grad, correct in zip(gradients2, correct_results2): - np.testing.assert_allclose(grad, correct, atol=1e-3) - - # batch size - with self.subTest(msg="Batch size"): - correct_results = [[-0.84147098, 0.1682942]] - gradient = SPSAEstimatorGradient(estimator, epsilon=1e-6, batch_size=5, seed=123) - gradients = gradient.run([qc], [op], param_list).result().gradients - np.testing.assert_allclose(gradients, correct_results, atol=1e-3) - - # parameter order - with self.subTest(msg="The order of gradients"): - gradient = SPSAEstimatorGradient(estimator, epsilon=1e-6, seed=123) - c = Parameter("c") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.rz(b, 0) - qc.rx(c, 0) - op = SparsePauliOp.from_list([("Z", 1)]) - param_list3 = [[np.pi / 4, np.pi / 2, np.pi / 3]] - param = [[a, b, c], [c, b, a], [a, c], [c, a]] - expected = [ - [-0.3535525, 0.3535525, 0.3535525], - [0.3535525, 0.3535525, -0.3535525], - [-0.3535525, 0.3535525], - [0.3535525, -0.3535525], - ] - for i, p in enumerate(param): - gradient = SPSAEstimatorGradient(estimator, epsilon=1e-6, seed=123) - gradients = ( - gradient.run([qc], [op], param_list3, parameters=[p]).result().gradients[0] - ) - np.testing.assert_allclose(gradients, expected[i], atol=1e-3) - - @data(ParamShiftEstimatorGradient, LinCombEstimatorGradient) - def test_gradient_random_parameters(self, grad): - """Test param shift and lin comb w/ random parameters""" - rng = np.random.default_rng(123) - qc = RealAmplitudes(num_qubits=3, reps=1) - params = qc.parameters - qc.rx(3.0 * params[0] + params[1].sin(), 0) - qc.ry(params[0].exp() + 2 * params[1], 1) - qc.rz(params[0] * params[1] - params[2], 2) - qc.p(2 * params[0] + 1, 0) - qc.u(params[0].sin(), params[1] - 2, params[2] * params[3], 1) - qc.sx(2) - qc.rxx(params[0].sin(), 1, 2) - qc.ryy(params[1].cos(), 2, 0) - qc.rzz(params[2] * 2, 0, 1) - qc.crx(params[0].exp(), 1, 2) - qc.cry(params[1].arctan(), 2, 0) - qc.crz(params[2] * -2, 0, 1) - qc.dcx(0, 1) - qc.csdg(0, 1) - qc.ccx(0, 1, 2) - qc.iswap(0, 2) - qc.swap(1, 2) - qc.global_phase = params[0] * params[1] + params[2].cos().exp() - - size = 10 - op = SparsePauliOp(random_pauli_list(num_qubits=qc.num_qubits, size=size, seed=rng)) - op.coeffs = rng.normal(0, 10, size) - - estimator = Estimator() - findiff = FiniteDiffEstimatorGradient(estimator, 1e-6) - gradient = grad(estimator) - - num_tries = 10 - param_values = rng.normal(0, 2, (num_tries, qc.num_parameters)).tolist() - np.testing.assert_allclose( - findiff.run([qc] * num_tries, [op] * num_tries, param_values).result().gradients, - gradient.run([qc] * num_tries, [op] * num_tries, param_values).result().gradients, - rtol=1e-4, - ) - - @data((DerivativeType.IMAG, -1.0), (DerivativeType.COMPLEX, -1.0j)) - @unpack - def test_complex_gradient(self, derivative_type, expected_gradient_value): - """Tests if the ``LinCombEstimatorGradient`` has the correct value.""" - estimator = Estimator() - lcu = LinCombEstimatorGradient(estimator, derivative_type=derivative_type) - reverse = ReverseEstimatorGradient(derivative_type=derivative_type) - - for gradient in [lcu, reverse]: - with self.subTest(gradient=gradient): - c = QuantumCircuit(1) - c.rz(Parameter("p"), 0) - result = gradient.run([c], [Pauli("I")], [[0.0]]).result() - self.assertAlmostEqual(result.gradients[0][0], expected_gradient_value) - - @data( - FiniteDiffEstimatorGradient, - ParamShiftEstimatorGradient, - LinCombEstimatorGradient, - SPSAEstimatorGradient, - ) - def test_options(self, grad): - """Test estimator gradient's run options""" - a = Parameter("a") - qc = QuantumCircuit(1) - qc.rx(a, 0) - op = SparsePauliOp.from_list([("Z", 1)]) - estimator = Estimator(options={"shots": 100}) - with self.subTest("estimator"): - if grad is FiniteDiffEstimatorGradient or grad is SPSAEstimatorGradient: - gradient = grad(estimator, epsilon=1e-6) - else: - gradient = grad(estimator) - options = gradient.options - result = gradient.run([qc], [op], [[1]]).result() - self.assertEqual(result.options.get("shots"), 100) - self.assertEqual(options.get("shots"), 100) - - with self.subTest("gradient init"): - if grad is FiniteDiffEstimatorGradient or grad is SPSAEstimatorGradient: - gradient = grad(estimator, epsilon=1e-6, options={"shots": 200}) - else: - gradient = grad(estimator, options={"shots": 200}) - options = gradient.options - result = gradient.run([qc], [op], [[1]]).result() - self.assertEqual(result.options.get("shots"), 200) - self.assertEqual(options.get("shots"), 200) - - with self.subTest("gradient update"): - if grad is FiniteDiffEstimatorGradient or grad is SPSAEstimatorGradient: - gradient = grad(estimator, epsilon=1e-6, options={"shots": 200}) - else: - gradient = grad(estimator, options={"shots": 200}) - gradient.update_default_options(shots=100) - options = gradient.options - result = gradient.run([qc], [op], [[1]]).result() - self.assertEqual(result.options.get("shots"), 100) - self.assertEqual(options.get("shots"), 100) - - with self.subTest("gradient run"): - if grad is FiniteDiffEstimatorGradient or grad is SPSAEstimatorGradient: - gradient = grad(estimator, epsilon=1e-6, options={"shots": 200}) - else: - gradient = grad(estimator, options={"shots": 200}) - options = gradient.options - result = gradient.run([qc], [op], [[1]], shots=300).result() - self.assertEqual(result.options.get("shots"), 300) - # Only default + estimator options. Not run. - self.assertEqual(options.get("shots"), 200) - - @data( - FiniteDiffEstimatorGradient, - ParamShiftEstimatorGradient, - LinCombEstimatorGradient, - SPSAEstimatorGradient, - ) - def test_operations_preserved(self, gradient_cls): - """Test non-parameterized instructions are preserved and not unrolled.""" - x = Parameter("x") - circuit = QuantumCircuit(2) - circuit.initialize([0.5, 0.5, 0.5, 0.5]) # this should remain as initialize - circuit.crx(x, 0, 1) # this should get unrolled - - values = [np.pi / 2] - expect = -1 / (2 * np.sqrt(2)) - - observable = SparsePauliOp(["XX"]) - - ops = [] - - def operations_callback(op): - ops.append(op) - - estimator = LoggingEstimator(operations_callback=operations_callback) - - if gradient_cls in [SPSAEstimatorGradient, FiniteDiffEstimatorGradient]: - gradient = gradient_cls(estimator, epsilon=0.01) - else: - gradient = gradient_cls(estimator) - - job = gradient.run([circuit], [observable], [values]) - result = job.result() - - with self.subTest(msg="assert initialize is preserved"): - self.assertTrue(all("initialize" in ops_i[0].keys() for ops_i in ops)) - - with self.subTest(msg="assert result is correct"): - self.assertAlmostEqual(result.gradients[0].item(), expect, places=5) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/gradients/test_qfi.py b/test/python/algorithms/gradients/test_qfi.py deleted file mode 100644 index b0d05ac7030f..000000000000 --- a/test/python/algorithms/gradients/test_qfi.py +++ /dev/null @@ -1,151 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -# ============================================================================= - -"""Test QFI.""" - -import unittest -from ddt import ddt, data - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.algorithms.gradients import LinCombQGT, ReverseQGT, QFI, DerivativeType -from qiskit.circuit import Parameter -from qiskit.circuit.parametervector import ParameterVector -from qiskit.primitives import Estimator -from qiskit.test import QiskitTestCase - - -@ddt -class TestQFI(QiskitTestCase): - """Test QFI""" - - def setUp(self): - super().setUp() - self.estimator = Estimator() - self.lcu_qgt = LinCombQGT(self.estimator, derivative_type=DerivativeType.REAL) - self.reverse_qgt = ReverseQGT(derivative_type=DerivativeType.REAL) - - def test_qfi(self): - """Test if the quantum fisher information calculation is correct for a simple test case. - QFI = [[1, 0], [0, 1]] - [[0, 0], [0, cos^2(a)]] - """ - # create the circuit - a, b = Parameter("a"), Parameter("b") - qc = QuantumCircuit(1) - qc.h(0) - qc.rz(a, 0) - qc.rx(b, 0) - - param_list = [[np.pi / 4, 0.1], [np.pi, 0.1], [np.pi / 2, 0.1]] - correct_values = [[[1, 0], [0, 0.5]], [[1, 0], [0, 0]], [[1, 0], [0, 1]]] - - qfi = QFI(self.lcu_qgt) - for i, param in enumerate(param_list): - qfis = qfi.run([qc], [param]).result().qfis - np.testing.assert_allclose(qfis[0], correct_values[i], atol=1e-3) - - def test_qfi_phase_fix(self): - """Test the phase-fix argument in the QFI calculation""" - # create the circuit - a, b = Parameter("a"), Parameter("b") - qc = QuantumCircuit(1) - qc.h(0) - qc.rz(a, 0) - qc.rx(b, 0) - - param = [np.pi / 4, 0.1] - # test for different values - correct_values = [[1, 0], [0, 1]] - qgt = LinCombQGT(self.estimator, phase_fix=False) - qfi = QFI(qgt) - qfis = qfi.run([qc], [param]).result().qfis - np.testing.assert_allclose(qfis[0], correct_values, atol=1e-3) - - @data("lcu", "reverse") - def test_qfi_maxcut(self, qgt_kind): - """Test the QFI for a simple MaxCut problem. - - This is interesting because it contains the same parameters in different gates. - """ - # create maxcut circuit for the hamiltonian - # H = (I ^ I ^ Z ^ Z) + (I ^ Z ^ I ^ Z) + (Z ^ I ^ I ^ Z) + (I ^ Z ^ Z ^ I) - - x = ParameterVector("x", 2) - ansatz = QuantumCircuit(4) - - # initial hadamard layer - ansatz.h(ansatz.qubits) - - # e^{iZZ} layers - def expiz(qubit0, qubit1): - ansatz.cx(qubit0, qubit1) - ansatz.rz(2 * x[0], qubit1) - ansatz.cx(qubit0, qubit1) - - expiz(2, 1) - expiz(3, 0) - expiz(2, 0) - expiz(1, 0) - - # mixer layer with RX gates - for i in range(ansatz.num_qubits): - ansatz.rx(2 * x[1], i) - - reference = np.array([[16.0, -5.551], [-5.551, 18.497]]) - param = [0.4, 0.69] - - qgt = self.lcu_qgt if qgt_kind == "lcu" else self.reverse_qgt - qfi = QFI(qgt) - qfi_result = qfi.run([ansatz], [param]).result().qfis - np.testing.assert_array_almost_equal(qfi_result[0], reference, decimal=3) - - def test_options(self): - """Test QFI's options""" - a = Parameter("a") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qgt = LinCombQGT(estimator=self.estimator, options={"shots": 100}) - - with self.subTest("QGT"): - qfi = QFI(qgt=qgt) - options = qfi.options - result = qfi.run([qc], [[1]]).result() - self.assertEqual(result.options.get("shots"), 100) - self.assertEqual(options.get("shots"), 100) - - with self.subTest("QFI init"): - qfi = QFI(qgt=qgt, options={"shots": 200}) - result = qfi.run([qc], [[1]]).result() - options = qfi.options - self.assertEqual(result.options.get("shots"), 200) - self.assertEqual(options.get("shots"), 200) - - with self.subTest("QFI update"): - qfi = QFI(qgt, options={"shots": 200}) - qfi.update_default_options(shots=100) - options = qfi.options - result = qfi.run([qc], [[1]]).result() - self.assertEqual(result.options.get("shots"), 100) - self.assertEqual(options.get("shots"), 100) - - with self.subTest("QFI run"): - qfi = QFI(qgt=qgt, options={"shots": 200}) - result = qfi.run([qc], [[0]], shots=300).result() - options = qfi.options - self.assertEqual(result.options.get("shots"), 300) - self.assertEqual(options.get("shots"), 200) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/gradients/test_qgt.py b/test/python/algorithms/gradients/test_qgt.py deleted file mode 100644 index 74f22d462d7c..000000000000 --- a/test/python/algorithms/gradients/test_qgt.py +++ /dev/null @@ -1,309 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -# ============================================================================= - -"""Test QGT.""" - -import unittest -from ddt import ddt, data - -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.algorithms.gradients import DerivativeType, LinCombQGT, ReverseQGT -from qiskit.circuit import Parameter -from qiskit.circuit.library import RealAmplitudes -from qiskit.primitives import Estimator -from qiskit.test import QiskitTestCase - -from .logging_primitives import LoggingEstimator - - -@ddt -class TestQGT(QiskitTestCase): - """Test QGT""" - - def setUp(self): - super().setUp() - self.estimator = Estimator() - - @data(LinCombQGT, ReverseQGT) - def test_qgt_derivative_type(self, qgt_type): - """Test QGT derivative_type""" - args = () if qgt_type == ReverseQGT else (self.estimator,) - qgt = qgt_type(*args, derivative_type=DerivativeType.REAL) - - a, b = Parameter("a"), Parameter("b") - qc = QuantumCircuit(1) - qc.h(0) - qc.rz(a, 0) - qc.rx(b, 0) - - param_list = [[np.pi / 4, 0], [np.pi / 2, np.pi / 4]] - correct_values = [ - np.array([[1, 0.707106781j], [-0.707106781j, 0.5]]) / 4, - np.array([[1, 1j], [-1j, 1]]) / 4, - ] - - # test real derivative - with self.subTest("Test with DerivativeType.REAL"): - qgt.derivative_type = DerivativeType.REAL - for i, param in enumerate(param_list): - qgt_result = qgt.run([qc], [param]).result().qgts - np.testing.assert_allclose(qgt_result[0], correct_values[i].real, atol=1e-3) - - # test imaginary derivative - with self.subTest("Test with DerivativeType.IMAG"): - qgt.derivative_type = DerivativeType.IMAG - for i, param in enumerate(param_list): - qgt_result = qgt.run([qc], [param]).result().qgts - np.testing.assert_allclose(qgt_result[0], correct_values[i].imag, atol=1e-3) - - # test real + imaginary derivative - with self.subTest("Test with DerivativeType.COMPLEX"): - qgt.derivative_type = DerivativeType.COMPLEX - for i, param in enumerate(param_list): - qgt_result = qgt.run([qc], [param]).result().qgts - np.testing.assert_allclose(qgt_result[0], correct_values[i], atol=1e-3) - - @data(LinCombQGT, ReverseQGT) - def test_qgt_phase_fix(self, qgt_type): - """Test the phase-fix argument in a QGT calculation""" - args = () if qgt_type == ReverseQGT else (self.estimator,) - qgt = qgt_type(*args, phase_fix=False) - - # create the circuit - a, b = Parameter("a"), Parameter("b") - qc = QuantumCircuit(1) - qc.h(0) - qc.rz(a, 0) - qc.rx(b, 0) - - param_list = [[np.pi / 4, 0], [np.pi / 2, np.pi / 4]] - correct_values = [ - np.array([[1, 0.707106781j], [-0.707106781j, 1]]) / 4, - np.array([[1, 1j], [-1j, 1]]) / 4, - ] - - # test real derivative - with self.subTest("Test phase fix with DerivativeType.REAL"): - qgt.derivative_type = DerivativeType.REAL - for i, param in enumerate(param_list): - qgt_result = qgt.run([qc], [param]).result().qgts - np.testing.assert_allclose(qgt_result[0], correct_values[i].real, atol=1e-3) - - # test imaginary derivative - with self.subTest("Test phase fix with DerivativeType.IMAG"): - qgt.derivative_type = DerivativeType.IMAG - for i, param in enumerate(param_list): - qgt_result = qgt.run([qc], [param]).result().qgts - np.testing.assert_allclose(qgt_result[0], correct_values[i].imag, atol=1e-3) - - # test real + imaginary derivative - with self.subTest("Test phase fix with DerivativeType.COMPLEX"): - qgt.derivative_type = DerivativeType.COMPLEX - for i, param in enumerate(param_list): - qgt_result = qgt.run([qc], [param]).result().qgts - np.testing.assert_allclose(qgt_result[0], correct_values[i], atol=1e-3) - - @data(LinCombQGT, ReverseQGT) - def test_qgt_coefficients(self, qgt_type): - """Test the derivative option of QGT""" - args = () if qgt_type == ReverseQGT else (self.estimator,) - qgt = qgt_type(*args, derivative_type=DerivativeType.REAL) - - qc = RealAmplitudes(num_qubits=2, reps=1) - qc.rz(qc.parameters[0].exp() + 2 * qc.parameters[1], 0) - qc.rx(3.0 * qc.parameters[2] + qc.parameters[3].sin(), 1) - - # test imaginary derivative - param_list = [ - [np.pi / 4 for param in qc.parameters], - [np.pi / 2 for param in qc.parameters], - ] - correct_values = ( - np.array( - [ - [ - [5.707309, 4.2924833, 1.5295868, 0.1938604], - [4.2924833, 4.9142136, 0.75, 0.8838835], - [1.5295868, 0.75, 3.4430195, 0.0758252], - [0.1938604, 0.8838835, 0.0758252, 1.1357233], - ], - [ - [1.0, 0.0, 1.0, 0.0], - [0.0, 1.0, 0.0, 0.0], - [1.0, 0.0, 10.0, -0.0], - [0.0, 0.0, -0.0, 1.0], - ], - ] - ) - / 4 - ) - for i, param in enumerate(param_list): - qgt_result = qgt.run([qc], [param]).result().qgts - np.testing.assert_allclose(qgt_result[0], correct_values[i], atol=1e-3) - - @data(LinCombQGT, ReverseQGT) - def test_qgt_parameters(self, qgt_type): - """Test the QGT with specified parameters""" - args = () if qgt_type == ReverseQGT else (self.estimator,) - qgt = qgt_type(*args, derivative_type=DerivativeType.REAL) - - a = Parameter("a") - b = Parameter("b") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.ry(b, 0) - param_values = [np.pi / 4, np.pi / 4] - qgt_result = qgt.run([qc], [param_values], [[a]]).result().qgts - np.testing.assert_allclose(qgt_result[0], [[1 / 4]], atol=1e-3) - - with self.subTest("Test with different parameter orders"): - c = Parameter("c") - qc2 = QuantumCircuit(1) - qc2.rx(a, 0) - qc2.rz(b, 0) - qc2.rx(c, 0) - param_values = [np.pi / 4, np.pi / 4, np.pi / 4] - params = [[a, b, c], [c, b, a], [a, c], [b, a]] - expected = [ - np.array( - [ - [0.25, 0.0, 0.1767767], - [0.0, 0.125, -0.08838835], - [0.1767767, -0.08838835, 0.1875], - ] - ), - np.array( - [ - [0.1875, -0.08838835, 0.1767767], - [-0.08838835, 0.125, 0.0], - [0.1767767, 0.0, 0.25], - ] - ), - np.array([[0.25, 0.1767767], [0.1767767, 0.1875]]), - np.array([[0.125, 0.0], [0.0, 0.25]]), - ] - for i, param in enumerate(params): - qgt_result = qgt.run([qc2], [param_values], [param]).result().qgts - np.testing.assert_allclose(qgt_result[0], expected[i], atol=1e-3) - - @data(LinCombQGT, ReverseQGT) - def test_qgt_multi_arguments(self, qgt_type): - """Test the QGT for multiple arguments""" - args = () if qgt_type == ReverseQGT else (self.estimator,) - qgt = qgt_type(*args, derivative_type=DerivativeType.REAL) - - a = Parameter("a") - b = Parameter("b") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.ry(b, 0) - qc2 = QuantumCircuit(1) - qc2.rx(a, 0) - qc2.ry(b, 0) - - param_list = [[np.pi / 4], [np.pi / 2]] - correct_values = [[[1 / 4]], [[1 / 4, 0], [0, 0]]] - param_list = [[np.pi / 4, np.pi / 4], [np.pi / 2, np.pi / 2]] - qgt_results = qgt.run([qc, qc2], param_list, [[a], None]).result().qgts - for i, _ in enumerate(param_list): - np.testing.assert_allclose(qgt_results[i], correct_values[i], atol=1e-3) - - @data(LinCombQGT, ReverseQGT) - def test_qgt_validation(self, qgt_type): - """Test estimator QGT's validation""" - args = () if qgt_type == ReverseQGT else (self.estimator,) - qgt = qgt_type(*args) - - a = Parameter("a") - qc = QuantumCircuit(1) - qc.rx(a, 0) - parameter_values = [[np.pi / 4]] - with self.subTest("assert number of circuits does not match"): - with self.assertRaises(ValueError): - qgt.run([qc, qc], parameter_values) - with self.subTest("assert number of parameter values does not match"): - with self.assertRaises(ValueError): - qgt.run([qc], [[np.pi / 4], [np.pi / 2]]) - with self.subTest("assert number of parameters does not match"): - with self.assertRaises(ValueError): - qgt.run([qc], parameter_values, parameters=[[a], [a]]) - - def test_options(self): - """Test QGT's options""" - a = Parameter("a") - qc = QuantumCircuit(1) - qc.rx(a, 0) - estimator = Estimator(options={"shots": 100}) - - with self.subTest("estimator"): - qgt = LinCombQGT(estimator) - options = qgt.options - result = qgt.run([qc], [[1]]).result() - self.assertEqual(result.options.get("shots"), 100) - self.assertEqual(options.get("shots"), 100) - - with self.subTest("QGT init"): - qgt = LinCombQGT(estimator, options={"shots": 200}) - result = qgt.run([qc], [[1]]).result() - options = qgt.options - self.assertEqual(result.options.get("shots"), 200) - self.assertEqual(options.get("shots"), 200) - - with self.subTest("QGT update"): - qgt = LinCombQGT(estimator, options={"shots": 200}) - qgt.update_default_options(shots=100) - options = qgt.options - result = qgt.run([qc], [[1]]).result() - self.assertEqual(result.options.get("shots"), 100) - self.assertEqual(options.get("shots"), 100) - - with self.subTest("QGT run"): - qgt = LinCombQGT(estimator, options={"shots": 200}) - result = qgt.run([qc], [[0]], shots=300).result() - options = qgt.options - self.assertEqual(result.options.get("shots"), 300) - self.assertEqual(options.get("shots"), 200) - - def test_operations_preserved(self): - """Test non-parameterized instructions are preserved and not unrolled.""" - x, y = Parameter("x"), Parameter("y") - circuit = QuantumCircuit(2) - circuit.initialize([0.5, 0.5, 0.5, 0.5]) # this should remain as initialize - circuit.crx(x, 0, 1) # this should get unrolled - circuit.ry(y, 0) - - values = [np.pi / 2, np.pi] - expect = np.diag([0.25, 0.5]) / 4 - - ops = [] - - def operations_callback(op): - ops.append(op) - - estimator = LoggingEstimator(operations_callback=operations_callback) - qgt = LinCombQGT(estimator, derivative_type=DerivativeType.REAL) - - job = qgt.run([circuit], [values]) - result = job.result() - - with self.subTest(msg="assert initialize is preserved"): - self.assertTrue(all("initialize" in ops_i[0].keys() for ops_i in ops)) - - with self.subTest(msg="assert result is correct"): - np.testing.assert_allclose(result.qgts[0], expect, atol=1e-5) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/gradients/test_sampler_gradient.py b/test/python/algorithms/gradients/test_sampler_gradient.py deleted file mode 100644 index 2faf44df6466..000000000000 --- a/test/python/algorithms/gradients/test_sampler_gradient.py +++ /dev/null @@ -1,690 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -# ============================================================================= - -"""Test Sampler Gradients""" - -import unittest -from typing import List - -import numpy as np -from ddt import ddt, data - -from qiskit import QuantumCircuit -from qiskit.algorithms.gradients import ( - FiniteDiffSamplerGradient, - LinCombSamplerGradient, - ParamShiftSamplerGradient, - SPSASamplerGradient, -) -from qiskit.circuit import Parameter -from qiskit.circuit.library import EfficientSU2, RealAmplitudes -from qiskit.circuit.library.standard_gates import RXXGate -from qiskit.primitives import Sampler -from qiskit.result import QuasiDistribution -from qiskit.test import QiskitTestCase - -from .logging_primitives import LoggingSampler - -gradient_factories = [ - lambda sampler: FiniteDiffSamplerGradient(sampler, epsilon=1e-6, method="central"), - lambda sampler: FiniteDiffSamplerGradient(sampler, epsilon=1e-6, method="forward"), - lambda sampler: FiniteDiffSamplerGradient(sampler, epsilon=1e-6, method="backward"), - ParamShiftSamplerGradient, - LinCombSamplerGradient, -] - - -@ddt -class TestSamplerGradient(QiskitTestCase): - """Test Sampler Gradient""" - - @data(*gradient_factories) - def test_single_circuit(self, grad): - """Test the sampler gradient for a single circuit""" - sampler = Sampler() - a = Parameter("a") - qc = QuantumCircuit(1) - qc.h(0) - qc.p(a, 0) - qc.h(0) - qc.measure_all() - gradient = grad(sampler) - param_list = [[np.pi / 4], [0], [np.pi / 2]] - expected = [ - [{0: -0.5 / np.sqrt(2), 1: 0.5 / np.sqrt(2)}], - [{0: 0, 1: 0}], - [{0: -0.499999, 1: 0.499999}], - ] - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [param]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=1) - array2 = _quasi2array(expected[i], num_qubits=1) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - @data(*gradient_factories) - def test_gradient_p(self, grad): - """Test the sampler gradient for p""" - sampler = Sampler() - a = Parameter("a") - qc = QuantumCircuit(1) - qc.h(0) - qc.p(a, 0) - qc.h(0) - qc.measure_all() - gradient = grad(sampler) - param_list = [[np.pi / 4], [0], [np.pi / 2]] - expected = [ - [{0: -0.5 / np.sqrt(2), 1: 0.5 / np.sqrt(2)}], - [{0: 0, 1: 0}], - [{0: -0.499999, 1: 0.499999}], - ] - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [param]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=1) - array2 = _quasi2array(expected[i], num_qubits=1) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - @data(*gradient_factories) - def test_gradient_u(self, grad): - """Test the sampler gradient for u""" - sampler = Sampler() - a = Parameter("a") - b = Parameter("b") - c = Parameter("c") - qc = QuantumCircuit(1) - qc.h(0) - qc.u(a, b, c, 0) - qc.h(0) - qc.measure_all() - gradient = grad(sampler) - param_list = [[np.pi / 4, 0, 0], [np.pi / 4, np.pi / 4, np.pi / 4]] - expected = [ - [{0: -0.5 / np.sqrt(2), 1: 0.5 / np.sqrt(2)}, {0: 0, 1: 0}, {0: 0, 1: 0}], - [{0: -0.176777, 1: 0.176777}, {0: -0.426777, 1: 0.426777}, {0: -0.426777, 1: 0.426777}], - ] - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [param]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=1) - array2 = _quasi2array(expected[i], num_qubits=1) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - @data(*gradient_factories) - def test_gradient_efficient_su2(self, grad): - """Test the sampler gradient for EfficientSU2""" - sampler = Sampler() - qc = EfficientSU2(2, reps=1) - qc.measure_all() - gradient = grad(sampler) - param_list = [ - [np.pi / 4 for param in qc.parameters], - [np.pi / 2 for param in qc.parameters], - ] - expected = [ - [ - { - 0: -0.11963834764831836, - 1: -0.05713834764831845, - 2: -0.21875000000000003, - 3: 0.39552669529663675, - }, - { - 0: -0.32230339059327373, - 1: -0.031250000000000014, - 2: 0.2339150429449554, - 3: 0.11963834764831843, - }, - { - 0: 0.012944173824159189, - 1: -0.01294417382415923, - 2: 0.07544417382415919, - 3: -0.07544417382415919, - }, - { - 0: 0.2080266952966367, - 1: -0.03125000000000002, - 2: -0.11963834764831842, - 3: -0.057138347648318405, - }, - { - 0: -0.11963834764831838, - 1: 0.11963834764831838, - 2: -0.21875000000000003, - 3: 0.21875, - }, - { - 0: -0.2781092167691146, - 1: -0.0754441738241592, - 2: 0.27810921676911443, - 3: 0.07544417382415924, - }, - {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0}, - {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0}, - ], - [ - { - 0: -4.163336342344337e-17, - 1: 2.7755575615628914e-17, - 2: -4.163336342344337e-17, - 3: 0.0, - }, - {0: 0.0, 1: -1.3877787807814457e-17, 2: 4.163336342344337e-17, 3: 0.0}, - { - 0: -0.24999999999999994, - 1: 0.24999999999999994, - 2: 0.24999999999999994, - 3: -0.24999999999999994, - }, - { - 0: 0.24999999999999994, - 1: 0.24999999999999994, - 2: -0.24999999999999994, - 3: -0.24999999999999994, - }, - { - 0: -4.163336342344337e-17, - 1: 4.163336342344337e-17, - 2: -4.163336342344337e-17, - 3: 5.551115123125783e-17, - }, - { - 0: -0.24999999999999994, - 1: 0.24999999999999994, - 2: 0.24999999999999994, - 3: -0.24999999999999994, - }, - {0: 0.0, 1: 2.7755575615628914e-17, 2: 0.0, 3: 2.7755575615628914e-17}, - {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0}, - ], - ] - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [param]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=2) - array2 = _quasi2array(expected[i], num_qubits=2) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - @data(*gradient_factories) - def test_gradient_2qubit_gate(self, grad): - """Test the sampler gradient for 2 qubit gates""" - sampler = Sampler() - for gate in [RXXGate]: - param_list = [[np.pi / 4], [np.pi / 2]] - correct_results = [ - [{0: -0.5 / np.sqrt(2), 1: 0, 2: 0, 3: 0.5 / np.sqrt(2)}], - [{0: -0.5, 1: 0, 2: 0, 3: 0.5}], - ] - for i, param in enumerate(param_list): - a = Parameter("a") - qc = QuantumCircuit(2) - qc.append(gate(a), [qc.qubits[0], qc.qubits[1]], []) - qc.measure_all() - gradient = grad(sampler) - gradients = gradient.run([qc], [param]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=2) - array2 = _quasi2array(correct_results[i], num_qubits=2) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - @data(*gradient_factories) - def test_gradient_parameter_coefficient(self, grad): - """Test the sampler gradient for parameter variables with coefficients""" - sampler = Sampler() - qc = RealAmplitudes(num_qubits=2, reps=1) - qc.rz(qc.parameters[0].exp() + 2 * qc.parameters[1], 0) - qc.rx(3.0 * qc.parameters[0] + qc.parameters[1].sin(), 1) - qc.u(qc.parameters[0], qc.parameters[1], qc.parameters[3], 1) - qc.p(2 * qc.parameters[0] + 1, 0) - qc.rxx(qc.parameters[0] + 2, 0, 1) - qc.measure_all() - gradient = grad(sampler) - param_list = [[np.pi / 4 for _ in qc.parameters], [np.pi / 2 for _ in qc.parameters]] - correct_results = [ - [ - { - 0: 0.30014831912265927, - 1: -0.6634809704357856, - 2: 0.343589357193753, - 3: 0.019743294119373426, - }, - { - 0: 0.16470607453981906, - 1: -0.40996282450610577, - 2: 0.08791803062881773, - 3: 0.15733871933746948, - }, - { - 0: 0.27036068339663866, - 1: -0.273790986018701, - 2: 0.12752010079553433, - 3: -0.12408979817347202, - }, - { - 0: -0.2098616294167757, - 1: -0.2515823946449894, - 2: 0.21929102305386305, - 3: 0.24215300100790207, - }, - ], - [ - { - 0: -1.844810060881004, - 1: 0.04620532700836027, - 2: 1.6367366426074323, - 3: 0.16186809126521057, - }, - { - 0: 0.07296073407769421, - 1: -0.021774869186331716, - 2: 0.02177486918633173, - 3: -0.07296073407769456, - }, - { - 0: -0.07794369186049102, - 1: -0.07794369186049122, - 2: 0.07794369186049117, - 3: 0.07794369186049112, - }, - { - 0: 0.0, - 1: 0.0, - 2: 0.0, - 3: 0.0, - }, - ], - ] - - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [param]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=2) - array2 = _quasi2array(correct_results[i], num_qubits=2) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - @data(*gradient_factories) - def test_gradient_parameters(self, grad): - """Test the sampler gradient for parameters""" - sampler = Sampler() - a = Parameter("a") - b = Parameter("b") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.rz(b, 0) - qc.measure_all() - gradient = grad(sampler) - param_list = [[np.pi / 4, np.pi / 2]] - expected = [ - [{0: -0.5 / np.sqrt(2), 1: 0.5 / np.sqrt(2)}], - ] - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [param], parameters=[[a]]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=1) - array2 = _quasi2array(expected[i], num_qubits=1) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - # parameter order - with self.subTest(msg="The order of gradients"): - c = Parameter("c") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.rz(b, 0) - qc.rx(c, 0) - qc.measure_all() - param_values = [[np.pi / 4, np.pi / 2, np.pi / 3]] - params = [[a, b, c], [c, b, a], [a, c], [c, a]] - expected = [ - [ - {0: -0.17677666583387008, 1: 0.17677666583378482}, - {0: 0.3061861668168149, 1: -0.3061861668167012}, - {0: -0.3061861668168149, 1: 0.30618616681678645}, - ], - [ - {0: -0.3061861668168149, 1: 0.30618616681678645}, - {0: 0.3061861668168149, 1: -0.3061861668167012}, - {0: -0.17677666583387008, 1: 0.17677666583378482}, - ], - [ - {0: -0.17677666583387008, 1: 0.17677666583378482}, - {0: -0.3061861668168149, 1: 0.30618616681678645}, - ], - [ - {0: -0.3061861668168149, 1: 0.30618616681678645}, - {0: -0.17677666583387008, 1: 0.17677666583378482}, - ], - ] - for i, p in enumerate(params): - gradients = gradient.run([qc], param_values, parameters=[p]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=1) - array2 = _quasi2array(expected[i], num_qubits=1) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - @data(*gradient_factories) - def test_gradient_multi_arguments(self, grad): - """Test the sampler gradient for multiple arguments""" - sampler = Sampler() - a = Parameter("a") - b = Parameter("b") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.measure_all() - qc2 = QuantumCircuit(1) - qc2.rx(b, 0) - qc2.measure_all() - gradient = grad(sampler) - param_list = [[np.pi / 4], [np.pi / 2]] - correct_results = [ - [{0: -0.5 / np.sqrt(2), 1: 0.5 / np.sqrt(2)}], - [{0: -0.499999, 1: 0.499999}], - ] - gradients = gradient.run([qc, qc2], param_list).result().gradients - for i, q_dists in enumerate(gradients): - array1 = _quasi2array(q_dists, num_qubits=1) - array2 = _quasi2array(correct_results[i], num_qubits=1) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - # parameters - with self.subTest(msg="Different parameters"): - c = Parameter("c") - qc3 = QuantumCircuit(1) - qc3.rx(c, 0) - qc3.ry(a, 0) - qc3.measure_all() - param_list2 = [[np.pi / 4], [np.pi / 4, np.pi / 4], [np.pi / 4, np.pi / 4]] - gradients = ( - gradient.run([qc, qc3, qc3], param_list2, parameters=[[a], [c], None]) - .result() - .gradients - ) - correct_results = [ - [{0: -0.5 / np.sqrt(2), 1: 0.5 / np.sqrt(2)}], - [{0: -0.25, 1: 0.25}], - [{0: -0.25, 1: 0.25}, {0: -0.25, 1: 0.25}], - ] - for i, q_dists in enumerate(gradients): - array1 = _quasi2array(q_dists, num_qubits=1) - array2 = _quasi2array(correct_results[i], num_qubits=1) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - @data(*gradient_factories) - def test_gradient_validation(self, grad): - """Test sampler gradient's validation""" - sampler = Sampler() - a = Parameter("a") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.measure_all() - gradient = grad(sampler) - param_list = [[np.pi / 4], [np.pi / 2]] - with self.assertRaises(ValueError): - gradient.run([qc], param_list) - with self.assertRaises(ValueError): - gradient.run([qc, qc], param_list, parameters=[[a]]) - with self.assertRaises(ValueError): - gradient.run([qc], [[np.pi / 4, np.pi / 4]]) - - def test_spsa_gradient(self): - """Test the SPSA sampler gradient""" - sampler = Sampler() - with self.assertRaises(ValueError): - _ = SPSASamplerGradient(sampler, epsilon=-0.1) - - a = Parameter("a") - b = Parameter("b") - c = Parameter("c") - qc = QuantumCircuit(2) - qc.rx(b, 0) - qc.rx(a, 1) - qc.measure_all() - param_list = [[1, 2]] - correct_results = [ - [ - {0: 0.2273244, 1: -0.6480598, 2: 0.2273244, 3: 0.1934111}, - {0: -0.2273244, 1: 0.6480598, 2: -0.2273244, 3: -0.1934111}, - ], - ] - gradient = SPSASamplerGradient(sampler, epsilon=1e-6, seed=123) - for i, param in enumerate(param_list): - gradients = gradient.run([qc], [param]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=2) - array2 = _quasi2array(correct_results[i], num_qubits=2) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - # multi parameters - with self.subTest(msg="Multiple parameters"): - param_list2 = [[1, 2], [1, 2], [3, 4]] - correct_results2 = [ - [ - {0: 0.2273244, 1: -0.6480598, 2: 0.2273244, 3: 0.1934111}, - {0: -0.2273244, 1: 0.6480598, 2: -0.2273244, 3: -0.1934111}, - ], - [ - {0: -0.2273244, 1: 0.6480598, 2: -0.2273244, 3: -0.1934111}, - ], - [ - {0: -0.0141129, 1: -0.0564471, 2: -0.3642884, 3: 0.4348484}, - {0: 0.0141129, 1: 0.0564471, 2: 0.3642884, 3: -0.4348484}, - ], - ] - gradient = SPSASamplerGradient(sampler, epsilon=1e-6, seed=123) - gradients = ( - gradient.run([qc] * 3, param_list2, parameters=[None, [b], None]).result().gradients - ) - for i, result in enumerate(gradients): - array1 = _quasi2array(result, num_qubits=2) - array2 = _quasi2array(correct_results2[i], num_qubits=2) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - # batch size - with self.subTest(msg="Batch size"): - param_list = [[1, 1]] - gradient = SPSASamplerGradient(sampler, epsilon=1e-6, batch_size=4, seed=123) - gradients = gradient.run([qc], param_list).result().gradients - correct_results3 = [ - [ - { - 0: -0.1620149622932887, - 1: -0.25872053011771756, - 2: 0.3723827084675668, - 3: 0.04835278392088804, - }, - { - 0: -0.1620149622932887, - 1: 0.3723827084675668, - 2: -0.25872053011771756, - 3: 0.04835278392088804, - }, - ] - ] - for i, q_dists in enumerate(gradients): - array1 = _quasi2array(q_dists, num_qubits=2) - array2 = _quasi2array(correct_results3[i], num_qubits=2) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - # parameter order - with self.subTest(msg="The order of gradients"): - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.rz(b, 0) - qc.rx(c, 0) - qc.measure_all() - param_list = [[np.pi / 4, np.pi / 2, np.pi / 3]] - param = [[a, b, c], [c, b, a], [a, c], [c, a]] - correct_results = [ - [ - {0: -0.17677624757590138, 1: 0.17677624757590138}, - {0: 0.17677624757590138, 1: -0.17677624757590138}, - {0: 0.17677624757590138, 1: -0.17677624757590138}, - ], - [ - {0: 0.17677624757590138, 1: -0.17677624757590138}, - {0: 0.17677624757590138, 1: -0.17677624757590138}, - {0: -0.17677624757590138, 1: 0.17677624757590138}, - ], - [ - {0: -0.17677624757590138, 1: 0.17677624757590138}, - {0: 0.17677624757590138, 1: -0.17677624757590138}, - ], - [ - {0: 0.17677624757590138, 1: -0.17677624757590138}, - {0: -0.17677624757590138, 1: 0.17677624757590138}, - ], - ] - for i, p in enumerate(param): - gradient = SPSASamplerGradient(sampler, epsilon=1e-6, seed=123) - gradients = gradient.run([qc], param_list, parameters=[p]).result().gradients[0] - array1 = _quasi2array(gradients, num_qubits=1) - array2 = _quasi2array(correct_results[i], num_qubits=1) - np.testing.assert_allclose(array1, array2, atol=1e-3) - - @data(ParamShiftSamplerGradient, LinCombSamplerGradient) - def test_gradient_random_parameters(self, grad): - """Test param shift and lin comb w/ random parameters""" - rng = np.random.default_rng(123) - qc = RealAmplitudes(num_qubits=3, reps=1) - params = qc.parameters - qc.rx(3.0 * params[0] + params[1].sin(), 0) - qc.ry(params[0].exp() + 2 * params[1], 1) - qc.rz(params[0] * params[1] - params[2], 2) - qc.p(2 * params[0] + 1, 0) - qc.u(params[0].sin(), params[1] - 2, params[2] * params[3], 1) - qc.sx(2) - qc.rxx(params[0].sin(), 1, 2) - qc.ryy(params[1].cos(), 2, 0) - qc.rzz(params[2] * 2, 0, 1) - qc.crx(params[0].exp(), 1, 2) - qc.cry(params[1].arctan(), 2, 0) - qc.crz(params[2] * -2, 0, 1) - qc.dcx(0, 1) - qc.csdg(0, 1) - qc.ccx(0, 1, 2) - qc.iswap(0, 2) - qc.swap(1, 2) - qc.global_phase = params[0] * params[1] + params[2].cos().exp() - qc.measure_all() - - sampler = Sampler() - findiff = FiniteDiffSamplerGradient(sampler, 1e-6) - gradient = grad(sampler) - - num_qubits = qc.num_qubits - num_tries = 10 - param_values = rng.normal(0, 2, (num_tries, qc.num_parameters)).tolist() - result1 = findiff.run([qc] * num_tries, param_values).result().gradients - result2 = gradient.run([qc] * num_tries, param_values).result().gradients - self.assertEqual(len(result1), len(result2)) - for res1, res2 in zip(result1, result2): - array1 = _quasi2array(res1, num_qubits) - array2 = _quasi2array(res2, num_qubits) - np.testing.assert_allclose(array1, array2, rtol=1e-4) - - @data( - FiniteDiffSamplerGradient, - ParamShiftSamplerGradient, - LinCombSamplerGradient, - SPSASamplerGradient, - ) - def test_options(self, grad): - """Test sampler gradient's run options""" - a = Parameter("a") - qc = QuantumCircuit(1) - qc.rx(a, 0) - qc.measure_all() - sampler = Sampler(options={"shots": 100}) - with self.subTest("sampler"): - if grad is FiniteDiffSamplerGradient or grad is SPSASamplerGradient: - gradient = grad(sampler, epsilon=1e-6) - else: - gradient = grad(sampler) - options = gradient.options - result = gradient.run([qc], [[1]]).result() - self.assertEqual(result.options.get("shots"), 100) - self.assertEqual(options.get("shots"), 100) - - with self.subTest("gradient init"): - if grad is FiniteDiffSamplerGradient or grad is SPSASamplerGradient: - gradient = grad(sampler, epsilon=1e-6, options={"shots": 200}) - else: - gradient = grad(sampler, options={"shots": 200}) - options = gradient.options - result = gradient.run([qc], [[1]]).result() - self.assertEqual(result.options.get("shots"), 200) - self.assertEqual(options.get("shots"), 200) - - with self.subTest("gradient update"): - if grad is FiniteDiffSamplerGradient or grad is SPSASamplerGradient: - gradient = grad(sampler, epsilon=1e-6, options={"shots": 200}) - else: - gradient = grad(sampler, options={"shots": 200}) - gradient.update_default_options(shots=100) - options = gradient.options - result = gradient.run([qc], [[1]]).result() - self.assertEqual(result.options.get("shots"), 100) - self.assertEqual(options.get("shots"), 100) - - with self.subTest("gradient run"): - if grad is FiniteDiffSamplerGradient or grad is SPSASamplerGradient: - gradient = grad(sampler, epsilon=1e-6, options={"shots": 200}) - else: - gradient = grad(sampler, options={"shots": 200}) - options = gradient.options - result = gradient.run([qc], [[1]], shots=300).result() - self.assertEqual(result.options.get("shots"), 300) - # Only default + sampler options. Not run. - self.assertEqual(options.get("shots"), 200) - - @data( - FiniteDiffSamplerGradient, - ParamShiftSamplerGradient, - LinCombSamplerGradient, - SPSASamplerGradient, - ) - def test_operations_preserved(self, gradient_cls): - """Test non-parameterized instructions are preserved and not unrolled.""" - x = Parameter("x") - circuit = QuantumCircuit(2) - circuit.initialize(np.array([1, 1, 0, 0]) / np.sqrt(2)) # this should remain as initialize - circuit.crx(x, 0, 1) # this should get unrolled - circuit.measure_all() - - values = [np.pi / 2] - expect = [{0: 0, 1: -0.25, 2: 0, 3: 0.25}] - - ops = [] - - def operations_callback(op): - ops.append(op) - - sampler = LoggingSampler(operations_callback=operations_callback) - - if gradient_cls in [SPSASamplerGradient, FiniteDiffSamplerGradient]: - gradient = gradient_cls(sampler, epsilon=0.01) - else: - gradient = gradient_cls(sampler) - - job = gradient.run([circuit], [values]) - result = job.result() - - with self.subTest(msg="assert initialize is preserved"): - self.assertTrue(all("initialize" in ops_i[0].keys() for ops_i in ops)) - - with self.subTest(msg="assert result is correct"): - array1 = _quasi2array(result.gradients[0], num_qubits=2) - array2 = _quasi2array(expect, num_qubits=2) - np.testing.assert_allclose(array1, array2, atol=1e-5) - - -def _quasi2array(quasis: List[QuasiDistribution], num_qubits: int) -> np.ndarray: - ret = np.zeros((len(quasis), 2**num_qubits)) - for i, quasi in enumerate(quasis): - ret[i, list(quasi.keys())] = list(quasi.values()) - return ret - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/minimum_eigensolvers/__init__.py b/test/python/algorithms/minimum_eigensolvers/__init__.py deleted file mode 100644 index fdb172d367f0..000000000000 --- a/test/python/algorithms/minimum_eigensolvers/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. diff --git a/test/python/algorithms/minimum_eigensolvers/test_adapt_vqe.py b/test/python/algorithms/minimum_eigensolvers/test_adapt_vqe.py deleted file mode 100644 index 9509abf422f3..000000000000 --- a/test/python/algorithms/minimum_eigensolvers/test_adapt_vqe.py +++ /dev/null @@ -1,245 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test of the AdaptVQE minimum eigensolver""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase - -from ddt import ddt, data, unpack -import numpy as np - -from qiskit.algorithms.minimum_eigensolvers import VQE -from qiskit.algorithms.minimum_eigensolvers.adapt_vqe import AdaptVQE, TerminationCriterion -from qiskit.algorithms.optimizers import SLSQP -from qiskit.circuit import QuantumCircuit, QuantumRegister -from qiskit.circuit.library import EvolvedOperatorAnsatz -from qiskit.opflow import PauliSumOp -from qiskit.primitives import Estimator -from qiskit.quantum_info import SparsePauliOp -from qiskit.utils import algorithm_globals - - -@ddt -class TestAdaptVQE(QiskitAlgorithmsTestCase): - """Test of the AdaptVQE minimum eigensolver""" - - def setUp(self): - super().setUp() - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 42 - - with self.assertWarns(DeprecationWarning): - self.h2_op = PauliSumOp.from_list( - [ - ("IIII", -0.8105479805373266), - ("ZZII", -0.2257534922240251), - ("IIZI", +0.12091263261776641), - ("ZIZI", +0.12091263261776641), - ("IZZI", +0.17218393261915543), - ("IIIZ", +0.17218393261915546), - ("IZIZ", +0.1661454325638243), - ("ZZIZ", +0.1661454325638243), - ("IIZZ", -0.2257534922240251), - ("IZZZ", +0.16892753870087926), - ("ZZZZ", +0.17464343068300464), - ("IXIX", +0.04523279994605788), - ("ZXIX", +0.04523279994605788), - ("IXZX", -0.04523279994605788), - ("ZXZX", -0.04523279994605788), - ] - ) - self.excitation_pool = [ - PauliSumOp( - SparsePauliOp(["IIIY", "IIZY"], coeffs=[0.5 + 0.0j, -0.5 + 0.0j]), coeff=1.0 - ), - PauliSumOp( - SparsePauliOp(["ZYII", "IYZI"], coeffs=[-0.5 + 0.0j, 0.5 + 0.0j]), coeff=1.0 - ), - PauliSumOp( - SparsePauliOp( - ["ZXZY", "IXIY", "IYIX", "ZYZX", "IYZX", "ZYIX", "ZXIY", "IXZY"], - coeffs=[ - -0.125 + 0.0j, - 0.125 + 0.0j, - -0.125 + 0.0j, - 0.125 + 0.0j, - 0.125 + 0.0j, - -0.125 + 0.0j, - 0.125 + 0.0j, - -0.125 + 0.0j, - ], - ), - coeff=1.0, - ), - ] - self.initial_state = QuantumCircuit(QuantumRegister(4)) - self.initial_state.x(0) - self.initial_state.x(1) - self.ansatz = EvolvedOperatorAnsatz( - self.excitation_pool, initial_state=self.initial_state - ) - self.optimizer = SLSQP() - - def test_default(self): - """Default execution""" - calc = AdaptVQE(VQE(Estimator(), self.ansatz, self.optimizer)) - - with self.assertWarns(DeprecationWarning): - res = calc.compute_minimum_eigenvalue(operator=self.h2_op) - - expected_eigenvalue = -1.85727503 - - self.assertAlmostEqual(res.eigenvalue, expected_eigenvalue, places=6) - np.testing.assert_allclose(res.eigenvalue_history, [expected_eigenvalue], rtol=1e-6) - - def test_with_quantum_info(self): - """Test behavior with quantum_info-based operators.""" - ansatz = EvolvedOperatorAnsatz( - [op.primitive for op in self.excitation_pool], - initial_state=self.initial_state, - ) - - calc = AdaptVQE(VQE(Estimator(), ansatz, self.optimizer)) - res = calc.compute_minimum_eigenvalue(operator=self.h2_op.primitive) - - expected_eigenvalue = -1.85727503 - - self.assertAlmostEqual(res.eigenvalue, expected_eigenvalue, places=6) - np.testing.assert_allclose(res.eigenvalue_history, [expected_eigenvalue], rtol=1e-6) - - def test_converged(self): - """Test to check termination criteria""" - calc = AdaptVQE( - VQE(Estimator(), self.ansatz, self.optimizer), - gradient_threshold=1e-3, - ) - with self.assertWarns(DeprecationWarning): - res = calc.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertEqual(res.termination_criterion, TerminationCriterion.CONVERGED) - - def test_maximum(self): - """Test to check termination criteria""" - calc = AdaptVQE( - VQE(Estimator(), self.ansatz, self.optimizer), - max_iterations=1, - ) - with self.assertWarns(DeprecationWarning): - res = calc.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertEqual(res.termination_criterion, TerminationCriterion.MAXIMUM) - - def test_eigenvalue_threshold(self): - """Test for the eigenvalue_threshold attribute.""" - operator = SparsePauliOp.from_list( - [ - ("XX", 1.0), - ("ZX", -0.5), - ("XZ", -0.5), - ] - ) - ansatz = EvolvedOperatorAnsatz( - [ - SparsePauliOp.from_list([("YZ", 0.4)]), - SparsePauliOp.from_list([("ZY", 0.5)]), - ], - initial_state=QuantumCircuit(2), - ) - - calc = AdaptVQE( - VQE(Estimator(), ansatz, self.optimizer), - eigenvalue_threshold=1, - ) - res = calc.compute_minimum_eigenvalue(operator) - - self.assertEqual(res.termination_criterion, TerminationCriterion.CONVERGED) - - def test_threshold_attribute(self): - """Test the (pending deprecated) threshold attribute""" - with self.assertWarns(PendingDeprecationWarning): - calc = AdaptVQE( - VQE(Estimator(), self.ansatz, self.optimizer), - threshold=1e-3, - ) - with self.assertWarns(DeprecationWarning): - res = calc.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertEqual(res.termination_criterion, TerminationCriterion.CONVERGED) - - @data( - ([1, 1], True), - ([1, 11], False), - ([11, 1], False), - ([1, 12], False), - ([12, 2], False), - ([1, 1, 1], True), - ([1, 2, 1], False), - ([1, 2, 2], True), - ([1, 2, 21], False), - ([1, 12, 2], False), - ([11, 1, 2], False), - ([1, 2, 1, 1], True), - ([1, 2, 1, 2], True), - ([1, 2, 1, 21], False), - ([11, 2, 1, 2], False), - ([1, 11, 1, 111], False), - ([11, 1, 111, 1], False), - ([1, 2, 3, 1, 2, 3], True), - ([1, 2, 3, 4, 1, 2, 3], False), - ([11, 2, 3, 1, 2, 3], False), - ([1, 2, 3, 1, 2, 31], False), - ([1, 2, 3, 4, 1, 2, 3, 4], True), - ([11, 2, 3, 4, 1, 2, 3, 4], False), - ([1, 2, 3, 4, 1, 2, 3, 41], False), - ([1, 2, 3, 4, 5, 1, 2, 3, 4], False), - ) - @unpack - def test_cyclicity(self, seq, is_cycle): - """Test AdaptVQE index cycle detection""" - self.assertEqual(is_cycle, AdaptVQE._check_cyclicity(seq)) - - def test_vqe_solver(self): - """Test to check if the VQE solver remains the same or not""" - solver = VQE(Estimator(), self.ansatz, self.optimizer) - calc = AdaptVQE(solver) - with self.assertWarns(DeprecationWarning): - _ = calc.compute_minimum_eigenvalue(operator=self.h2_op) - self.assertEqual(solver.ansatz, calc.solver.ansatz) - - def test_gradient_calculation(self): - """Test to check if the gradient calculation""" - solver = VQE(Estimator(), QuantumCircuit(1), self.optimizer) - calc = AdaptVQE(solver) - calc._excitation_pool = [SparsePauliOp("X")] - res = calc._compute_gradients(operator=SparsePauliOp("Y"), theta=[]) - # compare with manually computed reference value - self.assertAlmostEqual(res[0][0], 2.0) - - def test_supports_aux_operators(self): - """Test that auxiliary operators are supported""" - calc = AdaptVQE(VQE(Estimator(), self.ansatz, self.optimizer)) - with self.assertWarns(DeprecationWarning): - res = calc.compute_minimum_eigenvalue(operator=self.h2_op, aux_operators=[self.h2_op]) - - expected_eigenvalue = -1.85727503 - - self.assertAlmostEqual(res.eigenvalue, expected_eigenvalue, places=6) - self.assertAlmostEqual(res.aux_operators_evaluated[0][0], expected_eigenvalue, places=6) - np.testing.assert_allclose(res.eigenvalue_history, [expected_eigenvalue], rtol=1e-6) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/minimum_eigensolvers/test_numpy_minimum_eigensolver.py b/test/python/algorithms/minimum_eigensolvers/test_numpy_minimum_eigensolver.py deleted file mode 100644 index f5724f77c152..000000000000 --- a/test/python/algorithms/minimum_eigensolvers/test_numpy_minimum_eigensolver.py +++ /dev/null @@ -1,240 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test NumPy minimum eigensolver""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -from ddt import ddt, data - -from qiskit.algorithms.minimum_eigensolvers import NumPyMinimumEigensolver -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info import Operator, SparsePauliOp - -H2_SPARSE_PAULI = SparsePauliOp( - ["II", "ZI", "IZ", "ZZ", "XX"], - coeffs=[ - -1.052373245772859, - 0.39793742484318045, - -0.39793742484318045, - -0.01128010425623538, - 0.18093119978423156, - ], -) - -H2_OP = Operator(H2_SPARSE_PAULI.to_matrix()) - -H2_PAULI = PauliSumOp(H2_SPARSE_PAULI) - - -@ddt -class TestNumPyMinimumEigensolver(QiskitAlgorithmsTestCase): - """Test NumPy minimum eigensolver""" - - def setUp(self): - super().setUp() - aux_op1 = Operator(SparsePauliOp(["II"], coeffs=[2.0]).to_matrix()) - aux_op2 = SparsePauliOp(["II", "ZZ", "YY", "XX"], coeffs=[0.5, 0.5, 0.5, -0.5]) - self.aux_ops_list = [aux_op1, aux_op2] - self.aux_ops_dict = {"aux_op1": aux_op1, "aux_op2": aux_op2} - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_cme(self, op): - """Basic test""" - algo = NumPyMinimumEigensolver() - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=self.aux_ops_list) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 2) - self.assertAlmostEqual(result.aux_operators_evaluated[0][0], 2) - self.assertAlmostEqual(result.aux_operators_evaluated[1][0], 0) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_cme_reuse(self, op): - """Test reuse""" - algo = NumPyMinimumEigensolver() - - with self.subTest("Test with no operator or aux_operators, give via compute method"): - result = algo.compute_minimum_eigenvalue(operator=op) - self.assertEqual(result.eigenvalue.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalue, -1.85727503) - self.assertIsNone(result.aux_operators_evaluated) - - with self.subTest("Test with added aux_operators"): - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=self.aux_ops_list) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 2) - self.assertAlmostEqual(result.aux_operators_evaluated[0][0], 2) - self.assertAlmostEqual(result.aux_operators_evaluated[1][0], 0) - - with self.subTest("Test with aux_operators removed"): - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=[]) - self.assertEqual(result.eigenvalue.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalue, -1.85727503) - self.assertIsNone(result.aux_operators_evaluated) - - with self.subTest("Test with aux_operators set again"): - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=self.aux_ops_list) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 2) - self.assertAlmostEqual(result.aux_operators_evaluated[0][0], 2) - self.assertAlmostEqual(result.aux_operators_evaluated[1][0], 0) - - with self.subTest("Test after setting first aux_operators as main operator"): - result = algo.compute_minimum_eigenvalue( - operator=self.aux_ops_list[0], aux_operators=[] - ) - self.assertAlmostEqual(result.eigenvalue, 2 + 0j) - self.assertIsNone(result.aux_operators_evaluated) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_cme_filter(self, op): - """Basic test""" - - # define filter criterion - # pylint: disable=unused-argument - def criterion(x, v, a_v): - return v >= -0.5 - - algo = NumPyMinimumEigensolver(filter_criterion=criterion) - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=self.aux_ops_list) - self.assertAlmostEqual(result.eigenvalue, -0.22491125 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 2) - self.assertAlmostEqual(result.aux_operators_evaluated[0][0], 2) - self.assertAlmostEqual(result.aux_operators_evaluated[1][0], 0) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_cme_filter_empty(self, op): - """Test with filter always returning False""" - - # define filter criterion - # pylint: disable=unused-argument - def criterion(x, v, a_v): - return False - - algo = NumPyMinimumEigensolver(filter_criterion=criterion) - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=self.aux_ops_list) - self.assertEqual(result.eigenvalue, None) - self.assertEqual(result.eigenstate, None) - self.assertEqual(result.aux_operators_evaluated, None) - - @data("X", "Y", "Z") - def test_cme_1q(self, op): - """Test for 1 qubit operator""" - algo = NumPyMinimumEigensolver() - operator = SparsePauliOp([op], coeffs=1.0) - result = algo.compute_minimum_eigenvalue(operator=operator) - self.assertAlmostEqual(result.eigenvalue, -1) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_cme_aux_ops_dict(self, op): - """Test dictionary compatibility of aux_operators""" - # Start with an empty dictionary - algo = NumPyMinimumEigensolver() - - with self.subTest("Test with an empty dictionary."): - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators={}) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertIsNone(result.aux_operators_evaluated) - - with self.subTest("Test with two auxiliary operators."): - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=self.aux_ops_dict) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 2) - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op1"][0], 2) - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op2"][0], 0) - - with self.subTest("Test with additional zero and None operators."): - extra_ops = {"None_op": None, "zero_op": 0, **self.aux_ops_dict} - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=extra_ops) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 3) - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op1"][0], 2) - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op2"][0], 0) - self.assertEqual(result.aux_operators_evaluated["zero_op"], (0.0, {"variance": 0})) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_aux_operators_list(self, op): - """Test list-based aux_operators.""" - algo = NumPyMinimumEigensolver() - - with self.subTest("Test with two auxiliary operators."): - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=self.aux_ops_list) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 2) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[1][0], 0, places=6) - # standard deviations - self.assertAlmostEqual(result.aux_operators_evaluated[0][1].pop("variance"), 0.0) - self.assertAlmostEqual(result.aux_operators_evaluated[1][1].pop("variance"), 0.0) - - with self.subTest("Test with additional zero and None operators."): - extra_ops = [*self.aux_ops_list, None, 0] - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=extra_ops) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 4) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[1][0], 0, places=6) - self.assertIsNone(result.aux_operators_evaluated[2], None) - self.assertEqual(result.aux_operators_evaluated[3][0], 0.0) - # standard deviations - self.assertAlmostEqual(result.aux_operators_evaluated[0][1].pop("variance"), 0.0) - self.assertAlmostEqual(result.aux_operators_evaluated[1][1].pop("variance"), 0.0) - self.assertEqual(result.aux_operators_evaluated[3][1].pop("variance"), 0.0) - - @data(H2_SPARSE_PAULI, H2_PAULI, H2_OP) - def test_aux_operators_dict(self, op): - """Test dict-based aux_operators.""" - algo = NumPyMinimumEigensolver() - - with self.subTest("Test with two auxiliary operators."): - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=self.aux_ops_dict) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 2) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op2"][0], 0, places=6) - # standard deviations - self.assertAlmostEqual( - result.aux_operators_evaluated["aux_op1"][1].pop("variance"), 0.0 - ) - self.assertAlmostEqual( - result.aux_operators_evaluated["aux_op2"][1].pop("variance"), 0.0 - ) - - with self.subTest("Test with additional zero and None operators."): - extra_ops = {**self.aux_ops_dict, "None_operator": None, "zero_operator": 0} - result = algo.compute_minimum_eigenvalue(operator=op, aux_operators=extra_ops) - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operators_evaluated), 3) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op2"][0], 0, places=6) - self.assertEqual(result.aux_operators_evaluated["zero_operator"][0], 0.0) - self.assertTrue("None_operator" not in result.aux_operators_evaluated.keys()) - # standard deviations - self.assertAlmostEqual( - result.aux_operators_evaluated["aux_op1"][1].pop("variance"), 0.0 - ) - self.assertAlmostEqual( - result.aux_operators_evaluated["aux_op2"][1].pop("variance"), 0.0 - ) - self.assertAlmostEqual( - result.aux_operators_evaluated["zero_operator"][1].pop("variance"), 0.0 - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/minimum_eigensolvers/test_qaoa.py b/test/python/algorithms/minimum_eigensolvers/test_qaoa.py deleted file mode 100644 index c269f2aa0769..000000000000 --- a/test/python/algorithms/minimum_eigensolvers/test_qaoa.py +++ /dev/null @@ -1,304 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test the QAOA algorithm.""" - -import unittest -import warnings - -from functools import partial -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -import rustworkx as rx -from ddt import ddt, idata, unpack -from scipy.optimize import minimize as scipy_minimize - -from qiskit import QuantumCircuit -from qiskit.algorithms.minimum_eigensolvers import QAOA -from qiskit.algorithms.optimizers import COBYLA, NELDER_MEAD -from qiskit.circuit import Parameter -from qiskit.primitives import Sampler -from qiskit.quantum_info import Pauli, SparsePauliOp -from qiskit.result import QuasiDistribution -from qiskit.utils import algorithm_globals - - -W1 = np.array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]]) -P1 = 1 -M1 = SparsePauliOp.from_list( - [ - ("IIIX", 1), - ("IIXI", 1), - ("IXII", 1), - ("XIII", 1), - ] -) -S1 = {"0101", "1010"} - - -W2 = np.array( - [ - [0.0, 8.0, -9.0, 0.0], - [8.0, 0.0, 7.0, 9.0], - [-9.0, 7.0, 0.0, -8.0], - [0.0, 9.0, -8.0, 0.0], - ] -) -P2 = 1 -M2 = None -S2 = {"1011", "0100"} - -CUSTOM_SUPERPOSITION = [1 / np.sqrt(15)] * 15 + [0] - - -@ddt -class TestQAOA(QiskitAlgorithmsTestCase): - """Test QAOA with MaxCut.""" - - def setUp(self): - super().setUp() - self.seed = 10598 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - self.sampler = Sampler() - - @idata( - [ - [W1, P1, M1, S1], - [W2, P2, M2, S2], - ] - ) - @unpack - def test_qaoa(self, w, reps, mixer, solutions): - """QAOA test""" - self.log.debug("Testing %s-step QAOA with MaxCut on graph\n%s", reps, w) - - qubit_op, _ = self._get_operator(w) - - qaoa = QAOA(self.sampler, COBYLA(), reps=reps, mixer=mixer) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - self.assertIn(graph_solution, solutions) - - @idata( - [ - [W1, P1, S1], - [W2, P2, S2], - ] - ) - @unpack - def test_qaoa_qc_mixer(self, w, prob, solutions): - """QAOA test with a mixer as a parameterized circuit""" - self.log.debug( - "Testing %s-step QAOA with MaxCut on graph with a mixer as a parameterized circuit\n%s", - prob, - w, - ) - - optimizer = COBYLA() - qubit_op, _ = self._get_operator(w) - - num_qubits = qubit_op.num_qubits - mixer = QuantumCircuit(num_qubits) - theta = Parameter("θ") - mixer.rx(theta, range(num_qubits)) - - qaoa = QAOA(self.sampler, optimizer, reps=prob, mixer=mixer) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - self.assertIn(graph_solution, solutions) - - def test_qaoa_qc_mixer_many_parameters(self): - """QAOA test with a mixer as a parameterized circuit with the num of parameters > 1.""" - optimizer = COBYLA() - qubit_op, _ = self._get_operator(W1) - - num_qubits = qubit_op.num_qubits - mixer = QuantumCircuit(num_qubits) - for i in range(num_qubits): - theta = Parameter("θ" + str(i)) - mixer.rx(theta, range(num_qubits)) - - qaoa = QAOA(self.sampler, optimizer, reps=2, mixer=mixer) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - self.log.debug(x) - graph_solution = self._get_graph_solution(x) - self.assertIn(graph_solution, S1) - - def test_qaoa_qc_mixer_no_parameters(self): - """QAOA test with a mixer as a parameterized circuit with zero parameters.""" - qubit_op, _ = self._get_operator(W1) - - num_qubits = qubit_op.num_qubits - mixer = QuantumCircuit(num_qubits) - # just arbitrary circuit - mixer.rx(np.pi / 2, range(num_qubits)) - - qaoa = QAOA(self.sampler, COBYLA(), reps=1, mixer=mixer) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - # we just assert that we get a result, it is not meaningful. - self.assertIsNotNone(result.eigenstate) - - def test_change_operator_size(self): - """QAOA change operator size test""" - qubit_op, _ = self._get_operator( - np.array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]]) - ) - qaoa = QAOA(self.sampler, COBYLA(), reps=1) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - with self.subTest(msg="QAOA 4x4"): - self.assertIn(graph_solution, {"0101", "1010"}) - - qubit_op, _ = self._get_operator( - np.array( - [ - [0, 1, 0, 1, 0, 1], - [1, 0, 1, 0, 1, 0], - [0, 1, 0, 1, 0, 1], - [1, 0, 1, 0, 1, 0], - [0, 1, 0, 1, 0, 1], - [1, 0, 1, 0, 1, 0], - ] - ) - ) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - with self.subTest(msg="QAOA 6x6"): - self.assertIn(graph_solution, {"010101", "101010"}) - - @idata([[W2, S2, None], [W2, S2, [0.0, 0.0]], [W2, S2, [1.0, 0.8]]]) - @unpack - def test_qaoa_initial_point(self, w, solutions, init_pt): - """Check first parameter value used is initial point as expected""" - qubit_op, _ = self._get_operator(w) - - first_pt = [] - - def cb_callback(eval_count, parameters, mean, metadata): - nonlocal first_pt - if eval_count == 1: - first_pt = list(parameters) - - qaoa = QAOA( - self.sampler, - COBYLA(), - initial_point=init_pt, - callback=cb_callback, - ) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - - with self.subTest("Initial Point"): - # If None the preferred random initial point of QAOA variational form - if init_pt is None: - self.assertLess(result.eigenvalue, -0.97) - else: - self.assertListEqual(init_pt, first_pt) - - with self.subTest("Solution"): - self.assertIn(graph_solution, solutions) - - def test_qaoa_random_initial_point(self): - """QAOA random initial point""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - w = rx.adjacency_matrix( - rx.undirected_gnp_random_graph(5, 0.5, seed=algorithm_globals.random_seed) - ) - qubit_op, _ = self._get_operator(w) - qaoa = QAOA(self.sampler, NELDER_MEAD(disp=True), reps=2) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - self.assertLess(result.eigenvalue, -0.97) - - def test_optimizer_scipy_callable(self): - """Test passing a SciPy optimizer directly as callable.""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - w = rx.adjacency_matrix( - rx.undirected_gnp_random_graph(5, 0.5, seed=algorithm_globals.random_seed) - ) - qubit_op, _ = self._get_operator(w) - qaoa = QAOA( - self.sampler, - partial(scipy_minimize, method="Nelder-Mead", options={"maxiter": 2}), - ) - result = qaoa.compute_minimum_eigenvalue(qubit_op) - self.assertEqual(result.cost_function_evals, 5) - - def _get_operator(self, weight_matrix): - """Generate Hamiltonian for the max-cut problem of a graph. - - Args: - weight_matrix (numpy.ndarray) : adjacency matrix. - - Returns: - PauliSumOp: operator for the Hamiltonian - float: a constant shift for the obj function. - - """ - num_nodes = weight_matrix.shape[0] - pauli_list = [] - shift = 0 - for i in range(num_nodes): - for j in range(i): - if weight_matrix[i, j] != 0: - x_p = np.zeros(num_nodes, dtype=bool) - z_p = np.zeros(num_nodes, dtype=bool) - z_p[i] = True - z_p[j] = True - pauli_list.append([0.5 * weight_matrix[i, j], Pauli((z_p, x_p))]) - shift -= 0.5 * weight_matrix[i, j] - lst = [(pauli[1].to_label(), pauli[0]) for pauli in pauli_list] - return SparsePauliOp.from_list(lst), shift - - def _get_graph_solution(self, x: np.ndarray) -> str: - """Get graph solution from binary string. - - Args: - x : binary string as numpy array. - - Returns: - a graph solution as string. - """ - - return "".join([str(int(i)) for i in 1 - x]) - - def _sample_most_likely(self, state_vector: QuasiDistribution) -> np.ndarray: - """Compute the most likely binary string from state vector. - Args: - state_vector: Quasi-distribution. - - Returns: - Binary string as numpy.ndarray of ints. - """ - values = list(state_vector.values()) - n = int(np.log2(len(values))) - k = np.argmax(np.abs(values)) - x = np.zeros(n) - for i in range(n): - x[i] = k % 2 - k >>= 1 - return x - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/minimum_eigensolvers/test_qaoa_opflow.py b/test/python/algorithms/minimum_eigensolvers/test_qaoa_opflow.py deleted file mode 100644 index 18527c85046e..000000000000 --- a/test/python/algorithms/minimum_eigensolvers/test_qaoa_opflow.py +++ /dev/null @@ -1,312 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test the QAOA algorithm with opflow.""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase - -from functools import partial -import numpy as np - -from scipy.optimize import minimize as scipy_minimize -from ddt import ddt, idata, unpack - -import rustworkx as rx - -from qiskit import QuantumCircuit -from qiskit.algorithms.minimum_eigensolvers import QAOA -from qiskit.algorithms.optimizers import COBYLA, NELDER_MEAD -from qiskit.circuit import Parameter -from qiskit.opflow import PauliSumOp -from qiskit.quantum_info import Pauli -from qiskit.result import QuasiDistribution -from qiskit.primitives import Sampler -from qiskit.utils import algorithm_globals - -I = PauliSumOp.from_list([("I", 1)]) -X = PauliSumOp.from_list([("X", 1)]) - -W1 = np.array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]]) -P1 = 1 -M1 = (I ^ I ^ I ^ X) + (I ^ I ^ X ^ I) + (I ^ X ^ I ^ I) + (X ^ I ^ I ^ I) -S1 = {"0101", "1010"} - - -W2 = np.array( - [ - [0.0, 8.0, -9.0, 0.0], - [8.0, 0.0, 7.0, 9.0], - [-9.0, 7.0, 0.0, -8.0], - [0.0, 9.0, -8.0, 0.0], - ] -) -P2 = 1 -M2 = None -S2 = {"1011", "0100"} - -CUSTOM_SUPERPOSITION = [1 / np.sqrt(15)] * 15 + [0] - - -@ddt -class TestQAOA(QiskitAlgorithmsTestCase): - """Test QAOA with MaxCut.""" - - def setUp(self): - super().setUp() - self.seed = 10598 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - self.sampler = Sampler() - - @idata( - [ - [W1, P1, M1, S1], - [W2, P2, M2, S2], - ] - ) - @unpack - def test_qaoa(self, w, reps, mixer, solutions): - """QAOA test""" - self.log.debug("Testing %s-step QAOA with MaxCut on graph\n%s", reps, w) - - qubit_op, _ = self._get_operator(w) - - qaoa = QAOA(self.sampler, COBYLA(), reps=reps, mixer=mixer) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - self.assertIn(graph_solution, solutions) - - @idata( - [ - [W1, P1, S1], - [W2, P2, S2], - ] - ) - @unpack - def test_qaoa_qc_mixer(self, w, prob, solutions): - """QAOA test with a mixer as a parameterized circuit""" - self.log.debug( - "Testing %s-step QAOA with MaxCut on graph with a mixer as a parameterized circuit\n%s", - prob, - w, - ) - - optimizer = COBYLA() - qubit_op, _ = self._get_operator(w) - - num_qubits = qubit_op.num_qubits - mixer = QuantumCircuit(num_qubits) - theta = Parameter("θ") - mixer.rx(theta, range(num_qubits)) - - qaoa = QAOA(self.sampler, optimizer, reps=prob, mixer=mixer) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - self.assertIn(graph_solution, solutions) - - def test_qaoa_qc_mixer_many_parameters(self): - """QAOA test with a mixer as a parameterized circuit with the num of parameters > 1.""" - optimizer = COBYLA() - qubit_op, _ = self._get_operator(W1) - - num_qubits = qubit_op.num_qubits - mixer = QuantumCircuit(num_qubits) - for i in range(num_qubits): - theta = Parameter("θ" + str(i)) - mixer.rx(theta, range(num_qubits)) - - qaoa = QAOA(self.sampler, optimizer, reps=2, mixer=mixer) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - self.log.debug(x) - graph_solution = self._get_graph_solution(x) - self.assertIn(graph_solution, S1) - - def test_qaoa_qc_mixer_no_parameters(self): - """QAOA test with a mixer as a parameterized circuit with zero parameters.""" - qubit_op, _ = self._get_operator(W1) - - num_qubits = qubit_op.num_qubits - mixer = QuantumCircuit(num_qubits) - # just arbitrary circuit - mixer.rx(np.pi / 2, range(num_qubits)) - - qaoa = QAOA(self.sampler, COBYLA(), reps=1, mixer=mixer) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - # we just assert that we get a result, it is not meaningful. - self.assertIsNotNone(result.eigenstate) - - def test_change_operator_size(self): - """QAOA change operator size test""" - qubit_op, _ = self._get_operator( - np.array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]]) - ) - qaoa = QAOA(self.sampler, COBYLA(), reps=1) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - with self.subTest(msg="QAOA 4x4"): - self.assertIn(graph_solution, {"0101", "1010"}) - - qubit_op, _ = self._get_operator( - np.array( - [ - [0, 1, 0, 1, 0, 1], - [1, 0, 1, 0, 1, 0], - [0, 1, 0, 1, 0, 1], - [1, 0, 1, 0, 1, 0], - [0, 1, 0, 1, 0, 1], - [1, 0, 1, 0, 1, 0], - ] - ) - ) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - with self.subTest(msg="QAOA 6x6"): - self.assertIn(graph_solution, {"010101", "101010"}) - - @idata([[W2, S2, None], [W2, S2, [0.0, 0.0]], [W2, S2, [1.0, 0.8]]]) - @unpack - def test_qaoa_initial_point(self, w, solutions, init_pt): - """Check first parameter value used is initial point as expected""" - qubit_op, _ = self._get_operator(w) - - first_pt = [] - - def cb_callback(eval_count, parameters, mean, metadata): - nonlocal first_pt - if eval_count == 1: - first_pt = list(parameters) - - qaoa = QAOA( - self.sampler, - COBYLA(), - initial_point=init_pt, - callback=cb_callback, - ) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - - with self.subTest("Initial Point"): - # If None the preferred random initial point of QAOA variational form - if init_pt is None: - self.assertLess(result.eigenvalue, -0.97) - else: - self.assertListEqual(init_pt, first_pt) - - with self.subTest("Solution"): - self.assertIn(graph_solution, solutions) - - def test_qaoa_random_initial_point(self): - """QAOA random initial point""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - w = rx.adjacency_matrix( - rx.undirected_gnp_random_graph(5, 0.5, seed=algorithm_globals.random_seed) - ) - qubit_op, _ = self._get_operator(w) - qaoa = QAOA(self.sampler, NELDER_MEAD(disp=True), reps=2) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - self.assertLess(result.eigenvalue, -0.97) - - def test_optimizer_scipy_callable(self): - """Test passing a SciPy optimizer directly as callable.""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - w = rx.adjacency_matrix( - rx.undirected_gnp_random_graph(5, 0.5, seed=algorithm_globals.random_seed) - ) - qubit_op, _ = self._get_operator(w) - qaoa = QAOA( - self.sampler, - partial(scipy_minimize, method="Nelder-Mead", options={"maxiter": 2}), - ) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(qubit_op) - self.assertEqual(result.cost_function_evals, 5) - - def _get_operator(self, weight_matrix): - """Generate Hamiltonian for the max-cut problem of a graph. - - Args: - weight_matrix (numpy.ndarray) : adjacency matrix. - - Returns: - PauliSumOp: operator for the Hamiltonian - float: a constant shift for the obj function. - - """ - num_nodes = weight_matrix.shape[0] - pauli_list = [] - shift = 0 - for i in range(num_nodes): - for j in range(i): - if weight_matrix[i, j] != 0: - x_p = np.zeros(num_nodes, dtype=bool) - z_p = np.zeros(num_nodes, dtype=bool) - z_p[i] = True - z_p[j] = True - pauli_list.append([0.5 * weight_matrix[i, j], Pauli((z_p, x_p))]) - shift -= 0.5 * weight_matrix[i, j] - opflow_list = [(pauli[1].to_label(), pauli[0]) for pauli in pauli_list] - with self.assertWarns(DeprecationWarning): - return PauliSumOp.from_list(opflow_list), shift - - def _get_graph_solution(self, x: np.ndarray) -> str: - """Get graph solution from binary string. - - Args: - x : binary string as numpy array. - - Returns: - a graph solution as string. - """ - - return "".join([str(int(i)) for i in 1 - x]) - - def _sample_most_likely(self, state_vector: QuasiDistribution) -> np.ndarray: - """Compute the most likely binary string from state vector. - Args: - state_vector: Quasi-distribution. - - Returns: - Binary string as numpy.ndarray of ints. - """ - values = list(state_vector.values()) - n = int(np.log2(len(values))) - k = np.argmax(np.abs(values)) - x = np.zeros(n) - for i in range(n): - x[i] = k % 2 - k >>= 1 - return x - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/minimum_eigensolvers/test_sampling_vqe.py b/test/python/algorithms/minimum_eigensolvers/test_sampling_vqe.py deleted file mode 100644 index 4a7eb1929ef9..000000000000 --- a/test/python/algorithms/minimum_eigensolvers/test_sampling_vqe.py +++ /dev/null @@ -1,287 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test the Sampler VQE.""" - - -import unittest -import warnings -from functools import partial -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -from ddt import data, ddt -from scipy.optimize import minimize as scipy_minimize - -from qiskit.algorithms import AlgorithmError -from qiskit.algorithms.minimum_eigensolvers import SamplingVQE -from qiskit.algorithms.optimizers import L_BFGS_B, QNSPSA, SLSQP, OptimizerResult -from qiskit.algorithms.state_fidelities import ComputeUncompute -from qiskit.circuit import ParameterVector, QuantumCircuit -from qiskit.circuit.library import RealAmplitudes, TwoLocal -from qiskit.opflow import PauliSumOp -from qiskit.primitives import Sampler -from qiskit.quantum_info import Operator, Pauli, SparsePauliOp -from qiskit.utils import algorithm_globals - - -# pylint: disable=invalid-name -def _mock_optimizer(fun, x0, jac=None, bounds=None, inputs=None): - """A mock of a callable that can be used as minimizer in the VQE. - - If ``inputs`` is given as a dictionary, stores the inputs in that dictionary. - """ - result = OptimizerResult() - result.x = np.zeros_like(x0) - result.fun = fun(result.x) - result.nit = 0 - - if inputs is not None: - inputs.update({"fun": fun, "x0": x0, "jac": jac, "bounds": bounds}) - return result - - -PAULI_OP = PauliSumOp(SparsePauliOp(["ZZ", "IZ", "II"], coeffs=[1, -0.5, 0.12])) -OP = Operator(PAULI_OP.to_matrix()) - - -@ddt -class TestSamplerVQE(QiskitAlgorithmsTestCase): - """Test VQE""" - - def setUp(self): - super().setUp() - self.optimal_value = -1.38 - self.optimal_bitstring = "10" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 42 - - @data(PAULI_OP, OP) - def test_exact_sampler(self, op): - """Test the VQE on BasicAer's statevector simulator.""" - thetas = ParameterVector("th", 4) - ansatz = QuantumCircuit(2) - ansatz.rx(thetas[0], 0) - ansatz.rx(thetas[1], 1) - ansatz.cz(0, 1) - ansatz.ry(thetas[2], 0) - ansatz.ry(thetas[3], 1) - - optimizer = L_BFGS_B() - - # start in maximal superposition - initial_point = np.zeros(ansatz.num_parameters) - initial_point[-ansatz.num_qubits :] = np.pi / 2 - - vqe = SamplingVQE(Sampler(), ansatz, optimizer, initial_point=initial_point) - result = vqe.compute_minimum_eigenvalue(operator=op) - - with self.subTest(msg="test eigenvalue"): - self.assertAlmostEqual(result.eigenvalue, self.optimal_value, places=5) - - with self.subTest(msg="test optimal_value"): - self.assertAlmostEqual(result.optimal_value, self.optimal_value, places=5) - - with self.subTest(msg="test dimension of optimal point"): - self.assertEqual(len(result.optimal_point), ansatz.num_parameters) - - with self.subTest(msg="assert cost_function_evals is set"): - self.assertIsNotNone(result.cost_function_evals) - - with self.subTest(msg="assert optimizer_time is set"): - self.assertIsNotNone(result.optimizer_time) - - with self.subTest(msg="check best measurement"): - self.assertEqual(result.best_measurement["bitstring"], self.optimal_bitstring) - self.assertEqual(result.best_measurement["value"], self.optimal_value) - - @data(PAULI_OP, OP) - def test_invalid_initial_point(self, op): - """Test the proper error is raised when the initial point has the wrong size.""" - ansatz = RealAmplitudes(2, reps=1) - initial_point = np.array([1]) - - vqe = SamplingVQE(Sampler(), ansatz, SLSQP(), initial_point=initial_point) - - with self.assertRaises(ValueError): - _ = vqe.compute_minimum_eigenvalue(operator=op) - - @data(PAULI_OP, OP) - def test_ansatz_resize(self, op): - """Test the ansatz is properly resized if it's a blueprint circuit.""" - ansatz = RealAmplitudes(1, reps=1) - vqe = SamplingVQE(Sampler(), ansatz, SLSQP()) - result = vqe.compute_minimum_eigenvalue(operator=op) - self.assertAlmostEqual(result.eigenvalue, self.optimal_value, places=5) - - @data(PAULI_OP, OP) - def test_invalid_ansatz_size(self, op): - """Test an error is raised if the ansatz has the wrong number of qubits.""" - ansatz = QuantumCircuit(1) - ansatz.compose(RealAmplitudes(1, reps=2)) - vqe = SamplingVQE(Sampler(), ansatz, SLSQP()) - - with self.assertRaises(AlgorithmError): - _ = vqe.compute_minimum_eigenvalue(operator=op) - - @data(PAULI_OP, OP) - def test_missing_varform_params(self, op): - """Test specifying a variational form with no parameters raises an error.""" - circuit = QuantumCircuit(op.num_qubits) - vqe = SamplingVQE(Sampler(), circuit, SLSQP()) - with self.assertRaises(AlgorithmError): - vqe.compute_minimum_eigenvalue(operator=op) - - @data(PAULI_OP, OP) - def test_batch_evaluate_slsqp(self, op): - """Test batching with SLSQP (as representative of SciPyOptimizer).""" - optimizer = SLSQP(max_evals_grouped=10) - vqe = SamplingVQE(Sampler(), RealAmplitudes(), optimizer) - result = vqe.compute_minimum_eigenvalue(operator=op) - self.assertAlmostEqual(result.eigenvalue, self.optimal_value, places=5) - - def test_batch_evaluate_with_qnspsa(self): - """Test batch evaluating with QNSPSA works.""" - ansatz = TwoLocal(2, rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - - wrapped_sampler = Sampler() - inner_sampler = Sampler() - - callcount = {"count": 0} - - def wrapped_run(*args, **kwargs): - kwargs["callcount"]["count"] += 1 - return inner_sampler.run(*args, **kwargs) - - wrapped_sampler.run = partial(wrapped_run, callcount=callcount) - - fidelity = ComputeUncompute(wrapped_sampler) - - def fidelity_callable(left, right): - batchsize = np.asarray(left).shape[0] - job = fidelity.run(batchsize * [ansatz], batchsize * [ansatz], left, right) - return job.result().fidelities - - qnspsa = QNSPSA(fidelity_callable, maxiter=5) - qnspsa.set_max_evals_grouped(100) - - vqe = SamplingVQE(wrapped_sampler, ansatz, qnspsa) - _ = vqe.compute_minimum_eigenvalue(Pauli("ZZ")) - - # 1 calibration + 1 stddev estimation + 1 initial blocking - # + 5 (1 loss + 1 fidelity + 1 blocking) + 1 return loss + 1 VQE eval - expected = 1 + 1 + 1 + 5 * 3 + 1 + 1 - - self.assertEqual(callcount["count"], expected) - - def test_optimizer_scipy_callable(self): - """Test passing a SciPy optimizer directly as callable.""" - vqe = SamplingVQE( - Sampler(), - RealAmplitudes(), - partial(scipy_minimize, method="COBYLA", options={"maxiter": 2}), - ) - result = vqe.compute_minimum_eigenvalue(Pauli("Z")) - self.assertEqual(result.cost_function_evals, 2) - - def test_optimizer_callable(self): - """Test passing a optimizer directly as callable.""" - ansatz = RealAmplitudes(1, reps=1) - vqe = SamplingVQE(Sampler(), ansatz, _mock_optimizer) - result = vqe.compute_minimum_eigenvalue(Pauli("Z")) - self.assertTrue(np.all(result.optimal_point == np.zeros(ansatz.num_parameters))) - - @data(PAULI_OP, OP) - def test_auxops(self, op): - """Test passing auxiliary operators.""" - ansatz = RealAmplitudes(2, reps=1) - vqe = SamplingVQE(Sampler(), ansatz, SLSQP()) - - as_list = [Pauli("ZZ"), Pauli("II")] - with self.subTest(auxops=as_list): - result = vqe.compute_minimum_eigenvalue(op, aux_operators=as_list) - self.assertIsInstance(result.aux_operators_evaluated, list) - self.assertEqual(len(result.aux_operators_evaluated), 2) - self.assertAlmostEqual(result.aux_operators_evaluated[0][0], -1 + 0j, places=5) - self.assertAlmostEqual(result.aux_operators_evaluated[1][0], 1 + 0j, places=5) - - as_dict = {"magnetization": SparsePauliOp(["ZI", "IZ"])} - with self.subTest(auxops=as_dict): - result = vqe.compute_minimum_eigenvalue(op, aux_operators=as_dict) - self.assertIsInstance(result.aux_operators_evaluated, dict) - self.assertEqual(len(result.aux_operators_evaluated.keys()), 1) - self.assertAlmostEqual(result.aux_operators_evaluated["magnetization"][0], 0j, places=5) - - def test_nondiag_observable_raises(self): - """Test passing a non-diagonal observable raises an error.""" - vqe = SamplingVQE(Sampler(), RealAmplitudes(), SLSQP()) - - with self.assertRaises(ValueError): - _ = vqe.compute_minimum_eigenvalue(Pauli("X")) - - @data(PAULI_OP, OP) - def test_callback(self, op): - """Test the callback on VQE.""" - history = { - "eval_count": [], - "parameters": [], - "mean": [], - "metadata": [], - } - - def store_intermediate_result(eval_count, parameters, mean, metadata): - history["eval_count"].append(eval_count) - history["parameters"].append(parameters) - history["mean"].append(mean) - history["metadata"].append(metadata) - - sampling_vqe = SamplingVQE( - Sampler(), - RealAmplitudes(2, reps=1), - SLSQP(), - callback=store_intermediate_result, - ) - sampling_vqe.compute_minimum_eigenvalue(operator=op) - - self.assertTrue(all(isinstance(count, int) for count in history["eval_count"])) - self.assertTrue(all(isinstance(mean, complex) for mean in history["mean"])) - self.assertTrue(all(isinstance(metadata, dict) for metadata in history["metadata"])) - for params in history["parameters"]: - self.assertTrue(all(isinstance(param, float) for param in params)) - - def test_aggregation(self): - """Test the aggregation works.""" - - # test a custom aggregration that just uses the best measurement - def best_measurement(measurements): - res = min(measurements, key=lambda meas: meas[1])[1] - return res - - # test CVaR with alpha of 0.4 (i.e. 40% of the best measurements) - alpha = 0.4 - - ansatz = RealAmplitudes(1, reps=0) - ansatz.h(0) - - for aggregation in [alpha, best_measurement]: - with self.subTest(aggregation=aggregation): - vqe = SamplingVQE(Sampler(), ansatz, _mock_optimizer, aggregation=best_measurement) - result = vqe.compute_minimum_eigenvalue(Pauli("Z")) - - # evaluation at x0=0 samples -1 and 1 with 50% probability, and our aggregation - # takes the smallest value - self.assertAlmostEqual(result.optimal_value, -1) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/minimum_eigensolvers/test_vqe.py b/test/python/algorithms/minimum_eigensolvers/test_vqe.py deleted file mode 100644 index 210c622ca817..000000000000 --- a/test/python/algorithms/minimum_eigensolvers/test_vqe.py +++ /dev/null @@ -1,488 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test the variational quantum eigensolver algorithm.""" - -import unittest -import warnings -from test.python.algorithms import QiskitAlgorithmsTestCase - -from functools import partial -import numpy as np -from scipy.optimize import minimize as scipy_minimize -from ddt import data, ddt - -from qiskit import QuantumCircuit -from qiskit.algorithms import AlgorithmError -from qiskit.algorithms.gradients import ParamShiftEstimatorGradient -from qiskit.algorithms.minimum_eigensolvers import VQE -from qiskit.algorithms.optimizers import ( - CG, - COBYLA, - GradientDescent, - L_BFGS_B, - OptimizerResult, - P_BFGS, - QNSPSA, - SLSQP, - SPSA, - TNC, -) -from qiskit.algorithms.state_fidelities import ComputeUncompute -from qiskit.circuit.library import RealAmplitudes, TwoLocal -from qiskit.opflow import PauliSumOp, TwoQubitReduction -from qiskit.quantum_info import SparsePauliOp, Operator, Pauli -from qiskit.primitives import Estimator, Sampler -from qiskit.utils import algorithm_globals - -# pylint: disable=invalid-name -def _mock_optimizer(fun, x0, jac=None, bounds=None, inputs=None) -> OptimizerResult: - """A mock of a callable that can be used as minimizer in the VQE.""" - result = OptimizerResult() - result.x = np.zeros_like(x0) - result.fun = fun(result.x) - result.nit = 0 - - if inputs is not None: - inputs.update({"fun": fun, "x0": x0, "jac": jac, "bounds": bounds}) - return result - - -@ddt -class TestVQE(QiskitAlgorithmsTestCase): - """Test VQE""" - - def setUp(self): - super().setUp() - self.seed = 50 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - self.h2_op = SparsePauliOp( - ["II", "IZ", "ZI", "ZZ", "XX"], - coeffs=[ - -1.052373245772859, - 0.39793742484318045, - -0.39793742484318045, - -0.01128010425623538, - 0.18093119978423156, - ], - ) - self.h2_energy = -1.85727503 - - self.ryrz_wavefunction = TwoLocal(rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - self.ry_wavefunction = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz") - - @data(L_BFGS_B(), COBYLA()) - def test_basic_aer_statevector(self, estimator): - """Test VQE using reference Estimator.""" - vqe = VQE(Estimator(), self.ryrz_wavefunction, estimator) - - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - with self.subTest(msg="test eigenvalue"): - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - - with self.subTest(msg="test optimal_value"): - self.assertAlmostEqual(result.optimal_value, self.h2_energy) - - with self.subTest(msg="test dimension of optimal point"): - self.assertEqual(len(result.optimal_point), 16) - - with self.subTest(msg="assert cost_function_evals is set"): - self.assertIsNotNone(result.cost_function_evals) - - with self.subTest(msg="assert optimizer_time is set"): - self.assertIsNotNone(result.optimizer_time) - - with self.subTest(msg="assert optimizer_result is set"): - self.assertIsNotNone(result.optimizer_result) - - with self.subTest(msg="assert optimizer_result."): - self.assertAlmostEqual(result.optimizer_result.fun, self.h2_energy, places=5) - - with self.subTest(msg="assert return ansatz is set"): - estimator = Estimator() - job = estimator.run(result.optimal_circuit, self.h2_op, result.optimal_point) - np.testing.assert_array_almost_equal(job.result().values, result.eigenvalue, 6) - - def test_invalid_initial_point(self): - """Test the proper error is raised when the initial point has the wrong size.""" - ansatz = self.ryrz_wavefunction - initial_point = np.array([1]) - - vqe = VQE( - Estimator(), - ansatz, - SLSQP(), - initial_point=initial_point, - ) - - with self.assertRaises(ValueError): - _ = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - def test_ansatz_resize(self): - """Test the ansatz is properly resized if it's a blueprint circuit.""" - ansatz = RealAmplitudes(1, reps=1) - vqe = VQE(Estimator(), ansatz, SLSQP()) - result = vqe.compute_minimum_eigenvalue(self.h2_op) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - - def test_invalid_ansatz_size(self): - """Test an error is raised if the ansatz has the wrong number of qubits.""" - ansatz = QuantumCircuit(1) - ansatz.compose(RealAmplitudes(1, reps=2)) - vqe = VQE(Estimator(), ansatz, SLSQP()) - - with self.assertRaises(AlgorithmError): - _ = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - def test_missing_ansatz_params(self): - """Test specifying an ansatz with no parameters raises an error.""" - ansatz = QuantumCircuit(self.h2_op.num_qubits) - vqe = VQE(Estimator(), ansatz, SLSQP()) - with self.assertRaises(AlgorithmError): - vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - def test_max_evals_grouped(self): - """Test with SLSQP with max_evals_grouped.""" - optimizer = SLSQP(maxiter=50, max_evals_grouped=5) - vqe = VQE( - Estimator(), - self.ryrz_wavefunction, - optimizer, - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - - @data( - CG(), - L_BFGS_B(), - P_BFGS(), - SLSQP(), - TNC(), - ) - def test_with_gradient(self, optimizer): - """Test VQE using gradient primitive.""" - estimator = Estimator() - vqe = VQE( - estimator, - self.ry_wavefunction, - optimizer, - gradient=ParamShiftEstimatorGradient(estimator), - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - - def test_gradient_passed(self): - """Test the gradient is properly passed into the optimizer.""" - inputs = {} - estimator = Estimator() - vqe = VQE( - estimator, - RealAmplitudes(), - partial(_mock_optimizer, inputs=inputs), - gradient=ParamShiftEstimatorGradient(estimator), - ) - _ = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertIsNotNone(inputs["jac"]) - - def test_gradient_run(self): - """Test using the gradient to calculate the minimum.""" - estimator = Estimator() - vqe = VQE( - estimator, - RealAmplitudes(), - GradientDescent(maxiter=200, learning_rate=0.1), - gradient=ParamShiftEstimatorGradient(estimator), - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - - def test_with_two_qubit_reduction(self): - """Test the VQE using TwoQubitReduction.""" - with self.assertWarns(DeprecationWarning): - qubit_op = PauliSumOp.from_list( - [ - ("IIII", -0.8105479805373266), - ("IIIZ", 0.17218393261915552), - ("IIZZ", -0.22575349222402472), - ("IZZI", 0.1721839326191556), - ("ZZII", -0.22575349222402466), - ("IIZI", 0.1209126326177663), - ("IZZZ", 0.16892753870087912), - ("IXZX", -0.045232799946057854), - ("ZXIX", 0.045232799946057854), - ("IXIX", 0.045232799946057854), - ("ZXZX", -0.045232799946057854), - ("ZZIZ", 0.16614543256382414), - ("IZIZ", 0.16614543256382414), - ("ZZZZ", 0.17464343068300453), - ("ZIZI", 0.1209126326177663), - ] - ) - tapered_qubit_op = TwoQubitReduction(num_particles=2).convert(qubit_op) - vqe = VQE( - Estimator(), - self.ry_wavefunction, - SPSA(maxiter=300, last_avg=5), - ) - result = vqe.compute_minimum_eigenvalue(tapered_qubit_op) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=2) - - def test_callback(self): - """Test the callback on VQE.""" - history = {"eval_count": [], "parameters": [], "mean": [], "metadata": []} - - def store_intermediate_result(eval_count, parameters, mean, metadata): - history["eval_count"].append(eval_count) - history["parameters"].append(parameters) - history["mean"].append(mean) - history["metadata"].append(metadata) - - optimizer = COBYLA(maxiter=3) - wavefunction = self.ry_wavefunction - - estimator = Estimator() - - vqe = VQE( - estimator, - wavefunction, - optimizer, - callback=store_intermediate_result, - ) - vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertTrue(all(isinstance(count, int) for count in history["eval_count"])) - self.assertTrue(all(isinstance(mean, float) for mean in history["mean"])) - self.assertTrue(all(isinstance(metadata, dict) for metadata in history["metadata"])) - for params in history["parameters"]: - self.assertTrue(all(isinstance(param, float) for param in params)) - - def test_reuse(self): - """Test re-using a VQE algorithm instance.""" - ansatz = TwoLocal(rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - vqe = VQE(Estimator(), ansatz, SLSQP(maxiter=300)) - with self.subTest(msg="assert VQE works once all info is available"): - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - - operator = Operator(np.array([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, 2, 0], [0, 0, 0, 3]])) - - with self.subTest(msg="assert vqe works on re-use."): - result = vqe.compute_minimum_eigenvalue(operator=operator) - self.assertAlmostEqual(result.eigenvalue.real, -1.0, places=5) - - def test_vqe_optimizer_reuse(self): - """Test running same VQE twice to re-use optimizer, then switch optimizer""" - vqe = VQE( - Estimator(), - self.ryrz_wavefunction, - SLSQP(), - ) - - def run_check(): - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - - run_check() - - with self.subTest("Optimizer re-use."): - run_check() - - with self.subTest("Optimizer replace."): - vqe.optimizer = L_BFGS_B() - run_check() - - def test_default_batch_evaluation_on_spsa(self): - """Test the default batching works.""" - ansatz = TwoLocal(2, rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - - wrapped_estimator = Estimator() - inner_estimator = Estimator() - - callcount = {"estimator": 0} - - def wrapped_estimator_run(*args, **kwargs): - kwargs["callcount"]["estimator"] += 1 - return inner_estimator.run(*args, **kwargs) - - wrapped_estimator.run = partial(wrapped_estimator_run, callcount=callcount) - - spsa = SPSA(maxiter=5) - - vqe = VQE(wrapped_estimator, ansatz, spsa) - _ = vqe.compute_minimum_eigenvalue(Pauli("ZZ")) - - # 1 calibration + 5 loss + 1 return loss - expected_estimator_runs = 1 + 5 + 1 - - with self.subTest(msg="check callcount"): - self.assertEqual(callcount["estimator"], expected_estimator_runs) - - with self.subTest(msg="check reset to original max evals grouped"): - self.assertIsNone(spsa._max_evals_grouped) - - def test_batch_evaluate_with_qnspsa(self): - """Test batch evaluating with QNSPSA works.""" - ansatz = TwoLocal(2, rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - - wrapped_sampler = Sampler() - inner_sampler = Sampler() - - wrapped_estimator = Estimator() - inner_estimator = Estimator() - - callcount = {"sampler": 0, "estimator": 0} - - def wrapped_estimator_run(*args, **kwargs): - kwargs["callcount"]["estimator"] += 1 - return inner_estimator.run(*args, **kwargs) - - def wrapped_sampler_run(*args, **kwargs): - kwargs["callcount"]["sampler"] += 1 - return inner_sampler.run(*args, **kwargs) - - wrapped_estimator.run = partial(wrapped_estimator_run, callcount=callcount) - wrapped_sampler.run = partial(wrapped_sampler_run, callcount=callcount) - - fidelity = ComputeUncompute(wrapped_sampler) - - def fidelity_callable(left, right): - batchsize = np.asarray(left).shape[0] - job = fidelity.run(batchsize * [ansatz], batchsize * [ansatz], left, right) - return job.result().fidelities - - qnspsa = QNSPSA(fidelity_callable, maxiter=5) - qnspsa.set_max_evals_grouped(100) - - vqe = VQE( - wrapped_estimator, - ansatz, - qnspsa, - ) - _ = vqe.compute_minimum_eigenvalue(Pauli("ZZ")) - - # 5 (fidelity) - expected_sampler_runs = 5 - # 1 calibration + 1 stddev estimation + 1 initial blocking - # + 5 (1 loss + 1 blocking) + 1 return loss - expected_estimator_runs = 1 + 1 + 1 + 5 * 2 + 1 - - self.assertEqual(callcount["sampler"], expected_sampler_runs) - self.assertEqual(callcount["estimator"], expected_estimator_runs) - - def test_optimizer_scipy_callable(self): - """Test passing a SciPy optimizer directly as callable.""" - vqe = VQE( - Estimator(), - self.ryrz_wavefunction, - partial(scipy_minimize, method="L-BFGS-B", options={"maxiter": 10}), - ) - result = vqe.compute_minimum_eigenvalue(self.h2_op) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=2) - - def test_optimizer_callable(self): - """Test passing a optimizer directly as callable.""" - ansatz = RealAmplitudes(1, reps=1) - vqe = VQE(Estimator(), ansatz, _mock_optimizer) - result = vqe.compute_minimum_eigenvalue(SparsePauliOp("Z")) - self.assertTrue(np.all(result.optimal_point == np.zeros(ansatz.num_parameters))) - - def test_aux_operators_list(self): - """Test list-based aux_operators.""" - vqe = VQE(Estimator(), self.ry_wavefunction, SLSQP(maxiter=300)) - - with self.subTest("Test with an empty list."): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=[]) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertIsInstance(result.aux_operators_evaluated, list) - self.assertEqual(len(result.aux_operators_evaluated), 0) - - with self.subTest("Test with two auxiliary operators."): - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list( - [("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)] - ) - aux_ops = [aux_op1, aux_op2] - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=aux_ops) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - self.assertEqual(len(result.aux_operators_evaluated), 2) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0], 2.0, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[1][0], 0.0, places=6) - # metadata - self.assertIsInstance(result.aux_operators_evaluated[0][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[1][1], dict) - - with self.subTest("Test with additional zero operator."): - extra_ops = [*aux_ops, 0] - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=extra_ops) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - self.assertEqual(len(result.aux_operators_evaluated), 3) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated[0][0], 2.0, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[1][0], 0.0, places=6) - self.assertAlmostEqual(result.aux_operators_evaluated[2][0], 0.0) - # metadata - self.assertIsInstance(result.aux_operators_evaluated[0][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[1][1], dict) - self.assertIsInstance(result.aux_operators_evaluated[2][1], dict) - - def test_aux_operators_dict(self): - """Test dictionary compatibility of aux_operators""" - vqe = VQE(Estimator(), self.ry_wavefunction, SLSQP(maxiter=300)) - - with self.subTest("Test with an empty dictionary."): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators={}) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertIsInstance(result.aux_operators_evaluated, dict) - self.assertEqual(len(result.aux_operators_evaluated), 0) - - with self.subTest("Test with two auxiliary operators."): - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list( - [("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)] - ) - aux_ops = {"aux_op1": aux_op1, "aux_op2": aux_op2} - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=aux_ops) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertEqual(len(result.aux_operators_evaluated), 2) - - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op1"][0], 2.0, places=5) - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op2"][0], 0.0, places=5) - # metadata - self.assertIsInstance(result.aux_operators_evaluated["aux_op1"][1], dict) - self.assertIsInstance(result.aux_operators_evaluated["aux_op2"][1], dict) - - with self.subTest("Test with additional zero operator."): - extra_ops = {**aux_ops, "zero_operator": 0} - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=extra_ops) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertEqual(len(result.aux_operators_evaluated), 3) - # expectation values - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op1"][0], 2.0, places=5) - self.assertAlmostEqual(result.aux_operators_evaluated["aux_op2"][0], 0.0, places=5) - self.assertAlmostEqual(result.aux_operators_evaluated["zero_operator"][0], 0.0) - # metadata - self.assertIsInstance(result.aux_operators_evaluated["aux_op1"][1], dict) - self.assertIsInstance(result.aux_operators_evaluated["aux_op2"][1], dict) - self.assertIsInstance(result.aux_operators_evaluated["zero_operator"][1], dict) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/optimizers/__init__.py b/test/python/algorithms/optimizers/__init__.py deleted file mode 100644 index c6268f028739..000000000000 --- a/test/python/algorithms/optimizers/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Qiskit's algorithm optimizer tests.""" diff --git a/test/python/algorithms/optimizers/test_gradient_descent.py b/test/python/algorithms/optimizers/test_gradient_descent.py deleted file mode 100644 index 8d2396650176..000000000000 --- a/test/python/algorithms/optimizers/test_gradient_descent.py +++ /dev/null @@ -1,196 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Tests for the Gradient Descent optimizer.""" - -from test.python.algorithms import QiskitAlgorithmsTestCase -import numpy as np -from qiskit.algorithms.optimizers import GradientDescent, GradientDescentState -from qiskit.algorithms.optimizers.steppable_optimizer import TellData, AskData -from qiskit.circuit.library import PauliTwoDesign -from qiskit.opflow import I, Z, StateFn - - -class TestGradientDescent(QiskitAlgorithmsTestCase): - """Tests for the gradient descent optimizer.""" - - def setUp(self): - super().setUp() - np.random.seed(12) - self.initial_point = np.array([1, 1, 1, 1, 0]) - - def objective(self, x): - """Objective Function for the tests""" - return (np.linalg.norm(x) - 1) ** 2 - - def grad(self, x): - """Gradient of the objective function""" - return 2 * (np.linalg.norm(x) - 1) * x / np.linalg.norm(x) - - def test_pauli_two_design(self): - """Test standard gradient descent on the Pauli two-design example.""" - circuit = PauliTwoDesign(3, reps=3, seed=2) - parameters = list(circuit.parameters) - with self.assertWarns(DeprecationWarning): - obs = Z ^ Z ^ I - expr = ~StateFn(obs) @ StateFn(circuit) - - initial_point = np.array( - [ - 0.1822308, - -0.27254251, - 0.83684425, - 0.86153976, - -0.7111668, - 0.82766631, - 0.97867993, - 0.46136964, - 2.27079901, - 0.13382699, - 0.29589915, - 0.64883193, - ] - ) - - def objective_pauli(x): - return expr.bind_parameters(dict(zip(parameters, x))).eval().real - - optimizer = GradientDescent(maxiter=100, learning_rate=0.1, perturbation=0.1) - - with self.assertWarns(DeprecationWarning): - result = optimizer.minimize(objective_pauli, x0=initial_point) - self.assertLess(result.fun, -0.95) # final loss - self.assertEqual(result.nfev, 1300) # function evaluations - - def test_callback(self): - """Test the callback.""" - - history = [] - - def callback(*args): - history.append(args) - - optimizer = GradientDescent(maxiter=1, callback=callback) - - _ = optimizer.minimize(self.objective, np.array([1, -1])) - - self.assertEqual(len(history), 1) - self.assertIsInstance(history[0][0], int) # nfevs - self.assertIsInstance(history[0][1], np.ndarray) # parameters - self.assertIsInstance(history[0][2], float) # function value - self.assertIsInstance(history[0][3], float) # norm of the gradient - - def test_minimize(self): - """Test setting the learning rate as iterator and minimizing the funciton.""" - - def learning_rate(): - power = 0.6 - constant_coeff = 0.1 - - def powerlaw(): - n = 0 - while True: - yield constant_coeff * (n**power) - n += 1 - - return powerlaw() - - optimizer = GradientDescent(maxiter=20, learning_rate=learning_rate) - result = optimizer.minimize(self.objective, self.initial_point, self.grad) - - self.assertLess(result.fun, 1e-5) - - def test_no_start(self): - """Tests that making a step without having started the optimizer raises an error.""" - optimizer = GradientDescent() - with self.assertRaises(AttributeError): - optimizer.step() - - def test_start(self): - """Tests if the start method initializes the state properly.""" - optimizer = GradientDescent() - self.assertIsNone(optimizer.state) - self.assertIsNone(optimizer.perturbation) - optimizer.start(x0=self.initial_point, fun=self.objective) - - test_state = GradientDescentState( - x=self.initial_point, - fun=self.objective, - jac=None, - nfev=0, - njev=0, - nit=0, - learning_rate=1, - stepsize=None, - ) - - self.assertEqual(test_state, optimizer.state) - - def test_ask(self): - """Test the ask method.""" - optimizer = GradientDescent() - optimizer.start(fun=self.objective, x0=self.initial_point) - - ask_data = optimizer.ask() - np.testing.assert_equal(ask_data.x_jac, self.initial_point) - self.assertIsNone(ask_data.x_fun) - - def test_evaluate(self): - """Test the evaluate method.""" - optimizer = GradientDescent(perturbation=1e-10) - optimizer.start(fun=self.objective, x0=self.initial_point) - ask_data = AskData(x_jac=self.initial_point) - tell_data = optimizer.evaluate(ask_data=ask_data) - np.testing.assert_almost_equal(tell_data.eval_jac, self.grad(self.initial_point), decimal=2) - - def test_tell(self): - """Test the tell method.""" - optimizer = GradientDescent(learning_rate=1.0) - optimizer.start(fun=self.objective, x0=self.initial_point) - ask_data = AskData(x_jac=self.initial_point) - tell_data = TellData(eval_jac=self.initial_point) - optimizer.tell(ask_data=ask_data, tell_data=tell_data) - np.testing.assert_equal(optimizer.state.x, np.zeros(optimizer.state.x.shape)) - - def test_continue_condition(self): - """Test if the continue condition is working properly.""" - optimizer = GradientDescent(tol=1) - optimizer.start(fun=self.objective, x0=self.initial_point) - self.assertTrue(optimizer.continue_condition()) - optimizer.state.stepsize = 0.1 - self.assertFalse(optimizer.continue_condition()) - optimizer.state.stepsize = 10 - optimizer.state.nit = 1000 - self.assertFalse(optimizer.continue_condition()) - - def test_step(self): - """Tests if performing one step yields the desired result.""" - optimizer = GradientDescent(learning_rate=1.0) - optimizer.start(fun=self.objective, jac=self.grad, x0=self.initial_point) - optimizer.step() - np.testing.assert_almost_equal( - optimizer.state.x, self.initial_point - self.grad(self.initial_point), 6 - ) - - def test_wrong_dimension_gradient(self): - """Tests if an error is raised when a gradient of the wrong dimension is passed.""" - - optimizer = GradientDescent(learning_rate=1.0) - optimizer.start(fun=self.objective, x0=self.initial_point) - ask_data = AskData(x_jac=self.initial_point) - tell_data = TellData(eval_jac=np.array([1.0, 5])) - with self.assertRaises(ValueError): - optimizer.tell(ask_data=ask_data, tell_data=tell_data) - - tell_data = TellData(eval_jac=np.array(1)) - with self.assertRaises(ValueError): - optimizer.tell(ask_data=ask_data, tell_data=tell_data) diff --git a/test/python/algorithms/optimizers/test_optimizer_aqgd.py b/test/python/algorithms/optimizers/test_optimizer_aqgd.py deleted file mode 100644 index 9bef92b48cd4..000000000000 --- a/test/python/algorithms/optimizers/test_optimizer_aqgd.py +++ /dev/null @@ -1,122 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2023 -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test of AQGD optimizer""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase -from qiskit.circuit.library import RealAmplitudes -from qiskit.utils import QuantumInstance, algorithm_globals, optionals -from qiskit.opflow import PauliSumOp -from qiskit.algorithms.optimizers import AQGD -from qiskit.algorithms import VQE, AlgorithmError -from qiskit.opflow.gradients import Gradient -from qiskit.test import slow_test - - -class TestOptimizerAQGD(QiskitAlgorithmsTestCase): - """Test AQGD optimizer using RY for analytic gradient with VQE""" - - def setUp(self): - super().setUp() - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 50 - with self.assertWarns(DeprecationWarning): - self.qubit_op = PauliSumOp.from_list( - [ - ("II", -1.052373245772859), - ("IZ", 0.39793742484318045), - ("ZI", -0.39793742484318045), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423156), - ] - ) - - @slow_test - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test") - def test_simple(self): - """test AQGD optimizer with the parameters as single values.""" - from qiskit_aer import Aer - - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - Aer.get_backend("aer_simulator_statevector"), - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - aqgd = AQGD(momentum=0.0) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=RealAmplitudes(), - optimizer=aqgd, - gradient=Gradient("lin_comb"), - quantum_instance=q_instance, - ) - result = vqe.compute_minimum_eigenvalue(operator=self.qubit_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.857, places=3) - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test") - def test_list(self): - """test AQGD optimizer with the parameters as lists.""" - from qiskit_aer import Aer - - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - Aer.get_backend("aer_simulator_statevector"), - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - aqgd = AQGD(maxiter=[1000, 1000, 1000], eta=[1.0, 0.5, 0.3], momentum=[0.0, 0.5, 0.75]) - - with self.assertWarns(DeprecationWarning): - vqe = VQE(ansatz=RealAmplitudes(), optimizer=aqgd, quantum_instance=q_instance) - result = vqe.compute_minimum_eigenvalue(operator=self.qubit_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.857, places=3) - - def test_raises_exception(self): - """tests that AQGD raises an exception when incorrect values are passed.""" - self.assertRaises(AlgorithmError, AQGD, maxiter=[1000], eta=[1.0, 0.5], momentum=[0.0, 0.5]) - - @slow_test - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test") - def test_int_values(self): - """test AQGD with int values passed as eta and momentum.""" - from qiskit_aer import Aer - - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - Aer.get_backend("aer_simulator_statevector"), - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - aqgd = AQGD(maxiter=1000, eta=1, momentum=0) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=RealAmplitudes(), - optimizer=aqgd, - gradient=Gradient("lin_comb"), - quantum_instance=q_instance, - ) - result = vqe.compute_minimum_eigenvalue(operator=self.qubit_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.857, places=3) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/optimizers/test_optimizer_nft.py b/test/python/algorithms/optimizers/test_optimizer_nft.py deleted file mode 100644 index e904c3bffd91..000000000000 --- a/test/python/algorithms/optimizers/test_optimizer_nft.py +++ /dev/null @@ -1,64 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test of NFT optimizer""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase -from qiskit import BasicAer -from qiskit.circuit.library import RealAmplitudes -from qiskit.utils import QuantumInstance, algorithm_globals -from qiskit.opflow import PauliSumOp -from qiskit.algorithms.optimizers import NFT -from qiskit.algorithms import VQE - - -class TestOptimizerNFT(QiskitAlgorithmsTestCase): - """Test NFT optimizer using RY with VQE""" - - def setUp(self): - super().setUp() - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 50 - with self.assertWarns(DeprecationWarning): - self.qubit_op = PauliSumOp.from_list( - [ - ("II", -1.052373245772859), - ("IZ", 0.39793742484318045), - ("ZI", -0.39793742484318045), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423156), - ] - ) - - def test_nft(self): - """Test NFT optimizer by using it""" - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=RealAmplitudes(), - optimizer=NFT(), - quantum_instance=QuantumInstance( - BasicAer.get_backend("statevector_simulator"), - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ), - ) - result = vqe.compute_minimum_eigenvalue(operator=self.qubit_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.857275, places=6) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/optimizers/test_optimizers.py b/test/python/algorithms/optimizers/test_optimizers.py deleted file mode 100644 index f0e759ce4e4e..000000000000 --- a/test/python/algorithms/optimizers/test_optimizers.py +++ /dev/null @@ -1,404 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test Optimizers""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase - -from typing import Optional, List, Tuple -from ddt import ddt, data, unpack -import numpy as np -from scipy.optimize import rosen, rosen_der - -from qiskit.algorithms.optimizers import ( - ADAM, - AQGD, - BOBYQA, - IMFIL, - CG, - CRS, - COBYLA, - DIRECT_L, - DIRECT_L_RAND, - GSLS, - GradientDescent, - L_BFGS_B, - NELDER_MEAD, - Optimizer, - P_BFGS, - POWELL, - SLSQP, - SPSA, - QNSPSA, - TNC, - SciPyOptimizer, -) -from qiskit.circuit.library import RealAmplitudes -from qiskit.exceptions import MissingOptionalLibraryError -from qiskit.utils import algorithm_globals, optionals - - -@ddt -class TestOptimizers(QiskitAlgorithmsTestCase): - """Test Optimizers""" - - def setUp(self): - super().setUp() - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 52 - - def run_optimizer( - self, - optimizer: Optimizer, - max_nfev: int, - grad: bool = False, - bounds: Optional[List[Tuple[float, float]]] = None, - ): - """Test the optimizer. - - Args: - optimizer: The optimizer instance to test. - max_nfev: The maximal allowed number of function evaluations. - grad: Whether to pass the gradient function as input. - bounds: Optimizer bounds. - """ - x_0 = [1.3, 0.7, 0.8, 1.9, 1.2] - jac = rosen_der if grad else None - - res = optimizer.minimize(rosen, x_0, jac, bounds) - x_opt = res.x - nfev = res.nfev - - np.testing.assert_array_almost_equal(x_opt, [1.0] * len(x_0), decimal=2) - self.assertLessEqual(nfev, max_nfev) - - def test_adam(self): - """adam test""" - optimizer = ADAM(maxiter=10000, tol=1e-06) - self.run_optimizer(optimizer, max_nfev=10000) - - def test_cg(self): - """cg test""" - optimizer = CG(maxiter=1000, tol=1e-06) - self.run_optimizer(optimizer, max_nfev=10000) - - def test_gradient_descent(self): - """cg test""" - optimizer = GradientDescent(maxiter=100000, tol=1e-06, learning_rate=1e-3) - self.run_optimizer(optimizer, grad=True, max_nfev=100000) - - def test_cobyla(self): - """cobyla test""" - optimizer = COBYLA(maxiter=100000, tol=1e-06) - self.run_optimizer(optimizer, max_nfev=100000) - - def test_l_bfgs_b(self): - """l_bfgs_b test""" - optimizer = L_BFGS_B(maxfun=1000) - self.run_optimizer(optimizer, max_nfev=10000) - - def test_p_bfgs(self): - """parallel l_bfgs_b test""" - optimizer = P_BFGS(maxfun=1000, max_processes=4) - self.run_optimizer(optimizer, max_nfev=10000) - - def test_nelder_mead(self): - """nelder mead test""" - optimizer = NELDER_MEAD(maxfev=10000, tol=1e-06) - self.run_optimizer(optimizer, max_nfev=10000) - - def test_powell(self): - """powell test""" - optimizer = POWELL(maxfev=10000, tol=1e-06) - self.run_optimizer(optimizer, max_nfev=10000) - - def test_slsqp(self): - """slsqp test""" - optimizer = SLSQP(maxiter=1000, tol=1e-06) - self.run_optimizer(optimizer, max_nfev=10000) - - @unittest.skip("Skipping SPSA as it does not do well on non-convex rozen") - def test_spsa(self): - """spsa test""" - optimizer = SPSA(maxiter=10000) - self.run_optimizer(optimizer, max_nfev=100000) - - def test_tnc(self): - """tnc test""" - optimizer = TNC(maxiter=1000, tol=1e-06) - self.run_optimizer(optimizer, max_nfev=10000) - - def test_gsls(self): - """gsls test""" - optimizer = GSLS( - sample_size_factor=40, - sampling_radius=1.0e-12, - maxiter=10000, - max_eval=10000, - min_step_size=1.0e-12, - ) - x_0 = [1.3, 0.7, 0.8, 1.9, 1.2] - - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 1 - res = optimizer.minimize(rosen, x_0) - x_value = res.fun - n_evals = res.nfev - - # Ensure value is near-optimal - self.assertLessEqual(x_value, 0.01) - self.assertLessEqual(n_evals, 10000) - - def test_scipy_optimizer(self): - """scipy_optimizer test""" - optimizer = SciPyOptimizer("BFGS", options={"maxiter": 1000}) - self.run_optimizer(optimizer, max_nfev=10000) - - def test_scipy_optimizer_callback(self): - """scipy_optimizer callback test""" - values = [] - - def callback(x): - values.append(x) - - optimizer = SciPyOptimizer("BFGS", options={"maxiter": 1000}, callback=callback) - self.run_optimizer(optimizer, max_nfev=10000) - self.assertTrue(values) # Check the list is nonempty. - - # ESCH and ISRES do not do well with rosen - @data( - (CRS, True), - (DIRECT_L, True), - (DIRECT_L_RAND, True), - (CRS, False), - (DIRECT_L, False), - (DIRECT_L_RAND, False), - ) - @unpack - def test_nlopt(self, optimizer_cls, use_bound): - """NLopt test""" - bounds = [(-6, 6)] * 5 if use_bound else None - try: - optimizer = optimizer_cls() - optimizer.set_options(**{"max_evals": 50000}) - self.run_optimizer(optimizer, max_nfev=50000, bounds=bounds) - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - - -@ddt -class TestOptimizerSerialization(QiskitAlgorithmsTestCase): - """Tests concerning the serialization of optimizers.""" - - @data( - ("BFGS", {"maxiter": 100, "eps": np.array([0.1])}), - ("CG", {"maxiter": 200, "gtol": 1e-8}), - ("COBYLA", {"maxiter": 10}), - ("L_BFGS_B", {"maxiter": 30}), - ("NELDER_MEAD", {"maxiter": 0}), - ("NFT", {"maxiter": 100}), - ("P_BFGS", {"maxiter": 5}), - ("POWELL", {"maxiter": 1}), - ("SLSQP", {"maxiter": 400}), - ("TNC", {"maxiter": 20}), - ("dogleg", {"maxiter": 100}), - ("trust-constr", {"maxiter": 10}), - ("trust-ncg", {"maxiter": 100}), - ("trust-exact", {"maxiter": 120}), - ("trust-krylov", {"maxiter": 150}), - ) - @unpack - def test_scipy(self, method, options): - """Test the SciPyOptimizer is serializable.""" - - optimizer = SciPyOptimizer(method, options=options) - serialized = optimizer.settings - from_dict = SciPyOptimizer(**serialized) - - self.assertEqual(from_dict._method, method.lower()) - self.assertEqual(from_dict._options, options) - - def test_independent_reconstruction(self): - """Test the SciPyOptimizers don't reset all settings upon creating a new instance. - - COBYLA is used as representative example here.""" - - kwargs = {"coffee": "without sugar"} - options = {"tea": "with milk"} - optimizer = COBYLA(maxiter=1, options=options, **kwargs) - serialized = optimizer.settings - from_dict = COBYLA(**serialized) - - with self.subTest(msg="test attributes"): - self.assertEqual(from_dict.settings["maxiter"], 1) - - with self.subTest(msg="test options"): - # options should only contain values that are *not* already in the initializer - # (e.g. should not contain maxiter) - self.assertEqual(from_dict.settings["options"], {"tea": "with milk"}) - - with self.subTest(msg="test kwargs"): - self.assertEqual(from_dict.settings["coffee"], "without sugar") - - with self.subTest(msg="option ids differ"): - self.assertNotEqual(id(serialized["options"]), id(from_dict.settings["options"])) - - def test_adam(self): - """Test ADAM is serializable.""" - - adam = ADAM(maxiter=100, amsgrad=True) - settings = adam.settings - - self.assertEqual(settings["maxiter"], 100) - self.assertTrue(settings["amsgrad"]) - - def test_aqgd(self): - """Test AQGD is serializable.""" - - opt = AQGD(maxiter=[200, 100], eta=[0.2, 0.1], momentum=[0.25, 0.1]) - settings = opt.settings - - self.assertListEqual(settings["maxiter"], [200, 100]) - self.assertListEqual(settings["eta"], [0.2, 0.1]) - self.assertListEqual(settings["momentum"], [0.25, 0.1]) - - @unittest.skipIf(not optionals.HAS_SKQUANT, "Install scikit-quant to run this test.") - def test_bobyqa(self): - """Test BOBYQA is serializable.""" - - opt = BOBYQA(maxiter=200) - settings = opt.settings - - self.assertEqual(settings["maxiter"], 200) - - @unittest.skipIf(not optionals.HAS_SKQUANT, "Install scikit-quant to run this test.") - def test_imfil(self): - """Test IMFIL is serializable.""" - - opt = IMFIL(maxiter=200) - settings = opt.settings - - self.assertEqual(settings["maxiter"], 200) - - def test_gradient_descent(self): - """Test GradientDescent is serializable.""" - - opt = GradientDescent(maxiter=10, learning_rate=0.01) - settings = opt.settings - - self.assertEqual(settings["maxiter"], 10) - self.assertEqual(settings["learning_rate"], 0.01) - - def test_gsls(self): - """Test GSLS is serializable.""" - - opt = GSLS(maxiter=100, sampling_radius=1e-3) - settings = opt.settings - - self.assertEqual(settings["maxiter"], 100) - self.assertEqual(settings["sampling_radius"], 1e-3) - - def test_spsa(self): - """Test SPSA optimizer is serializable.""" - options = { - "maxiter": 100, - "blocking": True, - "allowed_increase": 0.1, - "second_order": True, - "learning_rate": 0.02, - "perturbation": 0.05, - "regularization": 0.1, - "resamplings": 2, - "perturbation_dims": 5, - "trust_region": False, - "initial_hessian": None, - "lse_solver": None, - "hessian_delay": 0, - "callback": None, - "termination_checker": None, - } - spsa = SPSA(**options) - - self.assertDictEqual(spsa.settings, options) - - def test_spsa_custom_iterators(self): - """Test serialization works with custom iterators for learning rate and perturbation.""" - rate = 0.99 - - def powerlaw(): - n = 0 - while True: - yield rate**n - n += 1 - - def steps(): - n = 1 - divide_after = 20 - epsilon = 0.5 - while True: - yield epsilon - n += 1 - if n % divide_after == 0: - epsilon /= 2 - - learning_rate = powerlaw() - expected_learning_rate = np.array([next(learning_rate) for _ in range(200)]) - - perturbation = steps() - expected_perturbation = np.array([next(perturbation) for _ in range(200)]) - - spsa = SPSA(maxiter=200, learning_rate=powerlaw, perturbation=steps) - settings = spsa.settings - - self.assertTrue(np.allclose(settings["learning_rate"], expected_learning_rate)) - self.assertTrue(np.allclose(settings["perturbation"], expected_perturbation)) - - def test_qnspsa(self): - """Test QN-SPSA optimizer is serializable.""" - ansatz = RealAmplitudes(1) - fidelity = QNSPSA.get_fidelity(ansatz) - options = { - "fidelity": fidelity, - "maxiter": 100, - "blocking": True, - "allowed_increase": 0.1, - "learning_rate": 0.02, - "perturbation": 0.05, - "regularization": 0.1, - "resamplings": 2, - "perturbation_dims": 5, - "lse_solver": None, - "initial_hessian": None, - "callback": None, - "termination_checker": None, - "hessian_delay": 0, - } - spsa = QNSPSA(**options) - - settings = spsa.settings - expected = options.copy() - - with self.subTest(msg="check constructed dictionary"): - self.assertDictEqual(settings, expected) - - reconstructed = QNSPSA(**settings) # pylint: disable=unexpected-keyword-arg - with self.subTest(msg="test reconstructed optimizer"): - self.assertDictEqual(reconstructed.settings, expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/optimizers/test_optimizers_scikitquant.py b/test/python/algorithms/optimizers/test_optimizers_scikitquant.py deleted file mode 100644 index f3d3f645d895..000000000000 --- a/test/python/algorithms/optimizers/test_optimizers_scikitquant.py +++ /dev/null @@ -1,118 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test of scikit-quant optimizers.""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase - -from ddt import ddt, data, unpack - -import numpy -from qiskit import BasicAer -from qiskit.circuit.library import RealAmplitudes -from qiskit.utils import QuantumInstance, algorithm_globals -from qiskit.exceptions import MissingOptionalLibraryError -from qiskit.opflow import PauliSumOp -from qiskit.algorithms import VQE -from qiskit.algorithms.optimizers import BOBYQA, SNOBFIT, IMFIL - - -@ddt -class TestOptimizers(QiskitAlgorithmsTestCase): - """Test scikit-quant optimizers.""" - - def setUp(self): - """Set the problem.""" - super().setUp() - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 50 - with self.assertWarns(DeprecationWarning): - self.qubit_op = PauliSumOp.from_list( - [ - ("II", -1.052373245772859), - ("IZ", 0.39793742484318045), - ("ZI", -0.39793742484318045), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423156), - ] - ) - - def _optimize(self, optimizer): - """launch vqe""" - with self.assertWarns(DeprecationWarning): - qe = QuantumInstance( - BasicAer.get_backend("statevector_simulator"), - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - with self.assertWarns(DeprecationWarning): - vqe = VQE(ansatz=RealAmplitudes(), optimizer=optimizer, quantum_instance=qe) - result = vqe.compute_minimum_eigenvalue(operator=self.qubit_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.857, places=1) - - def test_bobyqa(self): - """BOBYQA optimizer test.""" - try: - optimizer = BOBYQA(maxiter=150) - self._optimize(optimizer) - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - - @unittest.skipIf( - # NB: numpy.__version__ may contain letters, e.g. "1.26.0b1" - tuple(map(int, numpy.__version__.split(".")[:2])) >= (1, 24), - "scikit's SnobFit currently incompatible with NumPy 1.24.0.", - ) - def test_snobfit(self): - """SNOBFIT optimizer test.""" - try: - optimizer = SNOBFIT(maxiter=100, maxfail=100, maxmp=20) - self._optimize(optimizer) - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - - @unittest.skipIf( - # NB: numpy.__version__ may contain letters, e.g. "1.26.0b1" - tuple(map(int, numpy.__version__.split(".")[:2])) >= (1, 24), - "scikit's SnobFit currently incompatible with NumPy 1.24.0.", - ) - @data((None,), ([(-1, 1), (None, None)],)) - @unpack - def test_snobfit_missing_bounds(self, bounds): - """SNOBFIT optimizer test with missing bounds.""" - try: - optimizer = SNOBFIT() - with self.assertRaises(ValueError): - optimizer.minimize( - fun=lambda _: 1, # using dummy function (never called) - x0=[0.1, 0.1], # dummy initial point - bounds=bounds, - ) - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - - def test_imfil(self): - """IMFIL test.""" - try: - optimizer = IMFIL(maxiter=100) - self._optimize(optimizer) - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/optimizers/test_spsa.py b/test/python/algorithms/optimizers/test_spsa.py deleted file mode 100644 index dafa8c44b435..000000000000 --- a/test/python/algorithms/optimizers/test_spsa.py +++ /dev/null @@ -1,293 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Tests for the SPSA optimizer.""" - -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import ddt, data - -import numpy as np - -from qiskit.algorithms.optimizers import SPSA, QNSPSA -from qiskit.circuit.library import PauliTwoDesign -from qiskit.primitives import Estimator, Sampler -from qiskit.providers.basicaer import StatevectorSimulatorPy -from qiskit.opflow import I, Z, StateFn, MatrixExpectation -from qiskit.utils import algorithm_globals - - -@ddt -class TestSPSA(QiskitAlgorithmsTestCase): - """Tests for the SPSA optimizer.""" - - def setUp(self): - super().setUp() - np.random.seed(12) - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 12 - - # @slow_test - @data("spsa", "2spsa", "qnspsa") - def test_pauli_two_design(self, method): - """Test SPSA on the Pauli two-design example.""" - circuit = PauliTwoDesign(3, reps=1, seed=1) - parameters = list(circuit.parameters) - with self.assertWarns(DeprecationWarning): - obs = Z ^ Z ^ I - expr = ~StateFn(obs) @ StateFn(circuit) - - initial_point = np.array( - [0.82311034, 0.02611798, 0.21077064, 0.61842177, 0.09828447, 0.62013131] - ) - - def objective(x): - return expr.bind_parameters(dict(zip(parameters, x))).eval().real - - settings = {"maxiter": 100, "blocking": True, "allowed_increase": 0} - - if method == "2spsa": - settings["second_order"] = True - settings["regularization"] = 0.01 - expected_nfev = settings["maxiter"] * 5 + 1 - elif method == "qnspsa": - settings["fidelity"] = QNSPSA.get_fidelity(circuit) - settings["regularization"] = 0.001 - settings["learning_rate"] = 0.05 - settings["perturbation"] = 0.05 - - expected_nfev = settings["maxiter"] * 7 + 1 - else: - expected_nfev = settings["maxiter"] * 3 + 1 - - if method == "qnspsa": - spsa = QNSPSA(**settings) - else: - spsa = SPSA(**settings) - - with self.assertWarns(DeprecationWarning): - result = spsa.optimize(circuit.num_parameters, objective, initial_point=initial_point) - - with self.subTest("check final accuracy"): - self.assertLess(result[1], -0.95) # final loss - - with self.subTest("check number of function calls"): - self.assertEqual(result[2], expected_nfev) # function evaluations - - def test_recalibrate_at_optimize(self): - """Test SPSA calibrates anew upon each optimization run, if no autocalibration is set.""" - - def objective(x): - return -(x**2) - - spsa = SPSA(maxiter=1) - _ = spsa.minimize(objective, x0=np.array([0.5])) - - self.assertIsNone(spsa.learning_rate) - self.assertIsNone(spsa.perturbation) - - def test_learning_rate_perturbation_as_iterators(self): - """Test the learning rate and perturbation can be callables returning iterators.""" - - def get_learning_rate(): - def learning_rate(): - x = 0.99 - while True: - x *= x - yield x - - return learning_rate - - def get_perturbation(): - def perturbation(): - x = 0.99 - while True: - x *= x - yield max(x, 0.01) - - return perturbation - - def objective(x): - return (np.linalg.norm(x) - 2) ** 2 - - spsa = SPSA(learning_rate=get_learning_rate(), perturbation=get_perturbation()) - result = spsa.minimize(objective, np.array([0.5, 0.5])) - - self.assertAlmostEqual(np.linalg.norm(result.x), 2, places=2) - - def test_learning_rate_perturbation_as_arrays(self): - """Test the learning rate and perturbation can be arrays.""" - - learning_rate = np.linspace(1, 0, num=100, endpoint=False) ** 2 - perturbation = 0.01 * np.ones(100) - - def objective(x): - return (np.linalg.norm(x) - 2) ** 2 - - spsa = SPSA(learning_rate=learning_rate, perturbation=perturbation) - result = spsa.minimize(objective, x0=np.array([0.5, 0.5])) - - self.assertAlmostEqual(np.linalg.norm(result.x), 2, places=2) - - def test_termination_checker(self): - """Test the termination_callback""" - - def objective(x): - return np.linalg.norm(x) + np.random.rand(1) - - class TerminationChecker: - """Example termination checker""" - - def __init__(self): - self.values = [] - - def __call__(self, nfev, point, fvalue, stepsize, accepted) -> bool: - self.values.append(fvalue) - - if len(self.values) > 10: - return True - return False - - maxiter = 400 - spsa = SPSA(maxiter=maxiter, termination_checker=TerminationChecker()) - result = spsa.minimize(objective, x0=[0.5, 0.5]) - - self.assertLess(result.nit, maxiter) - - def test_callback(self): - """Test using the callback.""" - - def objective(x): - return (np.linalg.norm(x) - 2) ** 2 - - history = {"nfevs": [], "points": [], "fvals": [], "updates": [], "accepted": []} - - def callback(nfev, point, fval, update, accepted): - history["nfevs"].append(nfev) - history["points"].append(point) - history["fvals"].append(fval) - history["updates"].append(update) - history["accepted"].append(accepted) - - maxiter = 10 - spsa = SPSA(maxiter=maxiter, learning_rate=0.01, perturbation=0.01, callback=callback) - _ = spsa.minimize(objective, x0=np.array([0.5, 0.5])) - - expected_types = [int, np.ndarray, float, float, bool] - for i, (key, values) in enumerate(history.items()): - self.assertTrue(all(isinstance(value, expected_types[i]) for value in values)) - self.assertEqual(len(history[key]), maxiter) - - @data(1, 2, 3, 4) - def test_estimate_stddev(self, max_evals_grouped): - """Test the estimate_stddev - See https://github.com/Qiskit/qiskit-nature/issues/797""" - - def objective(x): - if len(x.shape) == 2: - return np.array([sum(x_i) for x_i in x]) - return sum(x) - - point = np.ones(5) - result = SPSA.estimate_stddev(objective, point, avg=10, max_evals_grouped=max_evals_grouped) - self.assertAlmostEqual(result, 0) - - def test_qnspsa_fidelity_deprecation(self): - """Test using a backend and expectation converter in get_fidelity warns.""" - ansatz = PauliTwoDesign(2, reps=1, seed=2) - - with self.assertWarns(DeprecationWarning): - QNSPSA.get_fidelity(ansatz, backend=StatevectorSimulatorPy()) - with self.assertWarns(DeprecationWarning): - QNSPSA.get_fidelity(ansatz, expectation=MatrixExpectation()) - - # No warning when used correctly. - QNSPSA.get_fidelity(ansatz) - - def test_qnspsa_fidelity_primitives(self): - """Test the primitives can be used in get_fidelity.""" - ansatz = PauliTwoDesign(2, reps=1, seed=2) - initial_point = np.random.random(ansatz.num_parameters) - - with self.subTest(msg="pass as kwarg"): - fidelity = QNSPSA.get_fidelity(ansatz, sampler=Sampler()) - result = fidelity(initial_point, initial_point) - - self.assertAlmostEqual(result[0], 1) - - # this test can be removed once backend and expectation are removed - with self.subTest(msg="pass positionally"): - fidelity = QNSPSA.get_fidelity(ansatz, Sampler()) - result = fidelity(initial_point, initial_point) - - self.assertAlmostEqual(result[0], 1) - - def test_qnspsa_max_evals_grouped(self): - """Test using max_evals_grouped with QNSPSA.""" - circuit = PauliTwoDesign(3, reps=1, seed=1) - num_parameters = circuit.num_parameters - - with self.assertWarns(DeprecationWarning): - obs = Z ^ Z ^ I - - estimator = Estimator(options={"seed": 12}) - - initial_point = np.array( - [0.82311034, 0.02611798, 0.21077064, 0.61842177, 0.09828447, 0.62013131] - ) - - def objective(x): - x = np.reshape(x, (-1, num_parameters)).tolist() - n = len(x) - return estimator.run(n * [circuit], n * [obs.primitive], x).result().values.real - - fidelity = QNSPSA.get_fidelity(circuit) - optimizer = QNSPSA(fidelity) - optimizer.maxiter = 1 - optimizer.learning_rate = 0.05 - optimizer.perturbation = 0.05 - optimizer.set_max_evals_grouped(50) # greater than 1 - - result = optimizer.minimize(objective, initial_point) - - with self.subTest("check final accuracy"): - self.assertAlmostEqual(result.fun[0], 0.473, places=3) - - with self.subTest("check number of function calls"): - expected_nfev = 8 # 7 * maxiter + 1 - self.assertEqual(result.nfev, expected_nfev) - - def test_point_sample(self): - """Test point sample function in QNSPSA""" - - def fidelity(x, _y): - x = np.asarray(x) - return np.ones_like(x, dtype=float) # some float - - def objective(x): - return x - - def get_perturbation(): - def perturbation(): - while True: - yield 1 - - return perturbation - - qnspsa = QNSPSA(fidelity, maxiter=1, learning_rate=0.1, perturbation=get_perturbation()) - initial_point = 1.0 - result = qnspsa.minimize(objective, initial_point) - - expected_nfev = 8 # 7 * maxiter + 1 - self.assertEqual(result.nfev, expected_nfev) diff --git a/test/python/algorithms/optimizers/test_umda.py b/test/python/algorithms/optimizers/test_umda.py deleted file mode 100644 index 26952881de88..000000000000 --- a/test/python/algorithms/optimizers/test_umda.py +++ /dev/null @@ -1,98 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Tests for the UMDA optimizer.""" - -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -from scipy.optimize import rosen - -from qiskit.algorithms.optimizers.umda import UMDA -from qiskit.utils import algorithm_globals - - -class TestUMDA(QiskitAlgorithmsTestCase): - """Tests for the UMDA optimizer.""" - - def test_get_set(self): - """Test if getters and setters work as expected""" - umda = UMDA(maxiter=1, size_gen=20) - umda.disp = True - umda.size_gen = 30 - umda.alpha = 0.6 - umda.maxiter = 100 - - self.assertTrue(umda.disp) - self.assertEqual(umda.size_gen, 30) - self.assertEqual(umda.alpha, 0.6) - self.assertEqual(umda.maxiter, 100) - - def test_settings(self): - """Test if the settings display works well""" - umda = UMDA(maxiter=1, size_gen=20) - umda.disp = True - umda.size_gen = 30 - umda.alpha = 0.6 - umda.maxiter = 100 - - set_ = { - "maxiter": 100, - "alpha": 0.6, - "size_gen": 30, - "callback": None, - } - - self.assertEqual(umda.settings, set_) - - def test_minimize(self): - """optimize function test""" - # UMDA is volatile so we need to set the seeds for the execution - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 52 - - optimizer = UMDA(maxiter=1000, size_gen=100) - x_0 = [1.3, 0.7, 1.5] - res = optimizer.minimize(rosen, x_0) - - self.assertIsNotNone(res.fun) - self.assertEqual(len(res.x), len(x_0)) - np.testing.assert_array_almost_equal(res.x, [1.0] * len(x_0), decimal=2) - - def test_callback(self): - """Test the callback.""" - - def objective(x): - return np.linalg.norm(x) - 1 - - nfevs, parameters, fvals = [], [], [] - - def store_history(*args): - nfevs.append(args[0]) - parameters.append(args[1]) - fvals.append(args[2]) - - optimizer = UMDA(maxiter=1, callback=store_history) - _ = optimizer.minimize(objective, x0=np.arange(5)) - - self.assertEqual(len(nfevs), 1) - self.assertIsInstance(nfevs[0], int) - - self.assertEqual(len(parameters), 1) - self.assertIsInstance(parameters[0], np.ndarray) - self.assertEqual(parameters[0].size, 5) - - self.assertEqual(len(fvals), 1) - self.assertIsInstance(fvals[0], float) diff --git a/test/python/algorithms/optimizers/utils/__init__.py b/test/python/algorithms/optimizers/utils/__init__.py deleted file mode 100644 index f3adc3e3b4da..000000000000 --- a/test/python/algorithms/optimizers/utils/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""Tests for Optimizer Utils.""" diff --git a/test/python/algorithms/optimizers/utils/test_learning_rate.py b/test/python/algorithms/optimizers/utils/test_learning_rate.py deleted file mode 100644 index 52acdbf98aaa..000000000000 --- a/test/python/algorithms/optimizers/utils/test_learning_rate.py +++ /dev/null @@ -1,54 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Tests for LearningRate.""" - -from test.python.algorithms import QiskitAlgorithmsTestCase -import numpy as np -from qiskit.algorithms.optimizers.optimizer_utils import LearningRate - - -class TestLearningRate(QiskitAlgorithmsTestCase): - """Tests for the LearningRate class.""" - - def setUp(self): - super().setUp() - np.random.seed(12) - self.initial_point = np.array([1, 1, 1, 1, 0]) - - def objective(self, x): - """Objective Function for the tests""" - return (np.linalg.norm(x) - 1) ** 2 - - def test_learning_rate(self): - """ - Tests if the learning rate is initialized properly for each kind of input: - float, list and iterator. - """ - constant_learning_rate_input = 0.01 - list_learning_rate_input = [0.01 * n for n in range(10)] - generator_learning_rate_input = lambda: (el for el in list_learning_rate_input) - - with self.subTest("Check constant learning rate."): - constant_learning_rate = LearningRate(learning_rate=constant_learning_rate_input) - for _ in range(5): - self.assertEqual(constant_learning_rate_input, next(constant_learning_rate)) - - with self.subTest("Check learning rate list."): - list_learning_rate = LearningRate(learning_rate=list_learning_rate_input) - for i in range(5): - self.assertEqual(list_learning_rate_input[i], next(list_learning_rate)) - - with self.subTest("Check learning rate generator."): - generator_learning_rate = LearningRate(generator_learning_rate_input) - for i in range(5): - self.assertEqual(list_learning_rate_input[i], next(generator_learning_rate)) diff --git a/test/python/algorithms/state_fidelities/__init__.py b/test/python/algorithms/state_fidelities/__init__.py deleted file mode 100644 index d8b7d587c4cc..000000000000 --- a/test/python/algorithms/state_fidelities/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Tests for the primitive-based fidelity interfaces.""" diff --git a/test/python/algorithms/state_fidelities/test_compute_uncompute.py b/test/python/algorithms/state_fidelities/test_compute_uncompute.py deleted file mode 100644 index f3b106d9b5d7..000000000000 --- a/test/python/algorithms/state_fidelities/test_compute_uncompute.py +++ /dev/null @@ -1,264 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Tests for Fidelity.""" - -import unittest - -import numpy as np - -from qiskit.circuit import QuantumCircuit, ParameterVector -from qiskit.circuit.library import RealAmplitudes -from qiskit.primitives import Sampler -from qiskit.algorithms.state_fidelities import ComputeUncompute -from qiskit.test import QiskitTestCase - - -class TestComputeUncompute(QiskitTestCase): - """Test Compute-Uncompute Fidelity class""" - - def setUp(self): - super().setUp() - parameters = ParameterVector("x", 2) - - rx_rotations = QuantumCircuit(2) - rx_rotations.rx(parameters[0], 0) - rx_rotations.rx(parameters[1], 1) - - ry_rotations = QuantumCircuit(2) - ry_rotations.ry(parameters[0], 0) - ry_rotations.ry(parameters[1], 1) - - plus = QuantumCircuit(2) - plus.h([0, 1]) - - zero = QuantumCircuit(2) - - rx_rotation = QuantumCircuit(2) - rx_rotation.rx(parameters[0], 0) - rx_rotation.h(1) - - self._circuit = [rx_rotations, ry_rotations, plus, zero, rx_rotation] - self._sampler = Sampler() - self._left_params = np.array([[0, 0], [np.pi / 2, 0], [0, np.pi / 2], [np.pi, np.pi]]) - self._right_params = np.array([[0, 0], [0, 0], [np.pi / 2, 0], [0, 0]]) - - def test_1param_pair(self): - """test for fidelity with one pair of parameters""" - fidelity = ComputeUncompute(self._sampler) - job = fidelity.run( - self._circuit[0], self._circuit[1], self._left_params[0], self._right_params[0] - ) - result = job.result() - np.testing.assert_allclose(result.fidelities, np.array([1.0])) - - def test_1param_pair_local(self): - """test for fidelity with one pair of parameters""" - fidelity = ComputeUncompute(self._sampler, local=True) - job = fidelity.run( - self._circuit[0], self._circuit[1], self._left_params[0], self._right_params[0] - ) - result = job.result() - np.testing.assert_allclose(result.fidelities, np.array([1.0])) - - def test_local(self): - """test difference between local and global fidelity""" - fidelity_global = ComputeUncompute(self._sampler, local=False) - fidelity_local = ComputeUncompute(self._sampler, local=True) - fidelities = [] - for fidelity in [fidelity_global, fidelity_local]: - job = fidelity.run(self._circuit[2], self._circuit[3]) - result = job.result() - fidelities.append(result.fidelities[0]) - np.testing.assert_allclose(fidelities, np.array([0.25, 0.5]), atol=1e-16) - - def test_4param_pairs(self): - """test for fidelity with four pairs of parameters""" - fidelity = ComputeUncompute(self._sampler) - n = len(self._left_params) - job = fidelity.run( - [self._circuit[0]] * n, [self._circuit[1]] * n, self._left_params, self._right_params - ) - results = job.result() - np.testing.assert_allclose(results.fidelities, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) - - def test_symmetry(self): - """test for fidelity with the same circuit""" - fidelity = ComputeUncompute(self._sampler) - n = len(self._left_params) - job_1 = fidelity.run( - [self._circuit[0]] * n, [self._circuit[0]] * n, self._left_params, self._right_params - ) - job_2 = fidelity.run( - [self._circuit[0]] * n, [self._circuit[0]] * n, self._right_params, self._left_params - ) - results_1 = job_1.result() - results_2 = job_2.result() - np.testing.assert_allclose(results_1.fidelities, results_2.fidelities, atol=1e-16) - - def test_no_params(self): - """test for fidelity without parameters""" - fidelity = ComputeUncompute(self._sampler) - job = fidelity.run([self._circuit[2]], [self._circuit[3]]) - results = job.result() - np.testing.assert_allclose(results.fidelities, np.array([0.25]), atol=1e-16) - - job = fidelity.run([self._circuit[2]], [self._circuit[3]], [], []) - results = job.result() - np.testing.assert_allclose(results.fidelities, np.array([0.25]), atol=1e-16) - - def test_left_param(self): - """test for fidelity with only left parameters""" - fidelity = ComputeUncompute(self._sampler) - n = len(self._left_params) - job = fidelity.run( - [self._circuit[1]] * n, [self._circuit[3]] * n, values_1=self._left_params - ) - results = job.result() - np.testing.assert_allclose(results.fidelities, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) - - def test_right_param(self): - """test for fidelity with only right parameters""" - fidelity = ComputeUncompute(self._sampler) - n = len(self._left_params) - job = fidelity.run( - [self._circuit[3]] * n, [self._circuit[1]] * n, values_2=self._left_params - ) - results = job.result() - np.testing.assert_allclose(results.fidelities, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) - - def test_not_set_circuits(self): - """test for fidelity with no circuits.""" - fidelity = ComputeUncompute(self._sampler) - with self.assertRaises(TypeError): - job = fidelity.run( - circuits_1=None, - circuits_2=None, - values_1=self._left_params, - values_2=self._right_params, - ) - job.result() - - def test_circuit_mismatch(self): - """test for fidelity with different number of left/right circuits.""" - fidelity = ComputeUncompute(self._sampler) - n = len(self._left_params) - with self.assertRaises(ValueError): - job = fidelity.run( - [self._circuit[0]] * n, - [self._circuit[1]] * (n + 1), - self._left_params, - self._right_params, - ) - job.result() - - def test_asymmetric_params(self): - """test for fidelity when the 2 circuits have different number of - left/right parameters.""" - - fidelity = ComputeUncompute(self._sampler) - n = len(self._left_params) - right_params = [[p] for p in self._right_params[:, 0]] - job = fidelity.run( - [self._circuit[0]] * n, [self._circuit[4]] * n, self._left_params, right_params - ) - result = job.result() - np.testing.assert_allclose(result.fidelities, np.array([0.5, 0.25, 0.25, 0.0]), atol=1e-16) - - def test_input_format(self): - """test for different input format variations""" - - fidelity = ComputeUncompute(self._sampler) - circuit = RealAmplitudes(2) - values = np.random.random(circuit.num_parameters) - shift = np.ones_like(values) * 0.01 - - # lists of circuits, lists of numpy arrays - job = fidelity.run([circuit], [circuit], [values], [values + shift]) - result_1 = job.result() - - # lists of circuits, lists of lists - shift_val = values + shift - job = fidelity.run([circuit], [circuit], [values.tolist()], [shift_val.tolist()]) - result_2 = job.result() - - # circuits, lists - shift_val = values + shift - job = fidelity.run(circuit, circuit, values.tolist(), shift_val.tolist()) - result_3 = job.result() - - # circuits, np.arrays - job = fidelity.run(circuit, circuit, values, values + shift) - result_4 = job.result() - - np.testing.assert_allclose(result_1.fidelities, result_2.fidelities, atol=1e-16) - np.testing.assert_allclose(result_1.fidelities, result_3.fidelities, atol=1e-16) - np.testing.assert_allclose(result_1.fidelities, result_4.fidelities, atol=1e-16) - - def test_input_measurements(self): - """test for fidelity with measurements on input circuits""" - fidelity = ComputeUncompute(self._sampler) - circuit_1 = self._circuit[0] - circuit_1.measure_all() - circuit_2 = self._circuit[1] - circuit_2.measure_all() - - job = fidelity.run(circuit_1, circuit_2, self._left_params[0], self._right_params[0]) - result = job.result() - np.testing.assert_allclose(result.fidelities, np.array([1.0])) - - def test_options(self): - """Test fidelity's run options""" - sampler_shots = Sampler(options={"shots": 1024}) - - with self.subTest("sampler"): - # Only options in sampler - fidelity = ComputeUncompute(sampler_shots) - options = fidelity.options - job = fidelity.run(self._circuit[2], self._circuit[3]) - result = job.result() - self.assertEqual(options.__dict__, {"shots": 1024}) - self.assertEqual(result.options.__dict__, {"shots": 1024}) - - with self.subTest("fidelity init"): - # Fidelity default options override sampler - # options and add new fields - fidelity = ComputeUncompute(sampler_shots, options={"shots": 2048, "dummy": 100}) - options = fidelity.options - job = fidelity.run(self._circuit[2], self._circuit[3]) - result = job.result() - self.assertEqual(options.__dict__, {"shots": 2048, "dummy": 100}) - self.assertEqual(result.options.__dict__, {"shots": 2048, "dummy": 100}) - - with self.subTest("fidelity update"): - # Update fidelity options - fidelity = ComputeUncompute(sampler_shots, options={"shots": 2048, "dummy": 100}) - fidelity.update_default_options(shots=100) - options = fidelity.options - job = fidelity.run(self._circuit[2], self._circuit[3]) - result = job.result() - self.assertEqual(options.__dict__, {"shots": 100, "dummy": 100}) - self.assertEqual(result.options.__dict__, {"shots": 100, "dummy": 100}) - - with self.subTest("fidelity run"): - # Run options override fidelity options - fidelity = ComputeUncompute(sampler_shots, options={"shots": 2048, "dummy": 100}) - job = fidelity.run(self._circuit[2], self._circuit[3], shots=50, dummy=None) - options = fidelity.options - result = job.result() - # Only default + sampler options. Not run. - self.assertEqual(options.__dict__, {"shots": 2048, "dummy": 100}) - self.assertEqual(result.options.__dict__, {"shots": 50, "dummy": None}) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_amplitude_estimators.py b/test/python/algorithms/test_amplitude_estimators.py deleted file mode 100644 index ea0a1af099ee..000000000000 --- a/test/python/algorithms/test_amplitude_estimators.py +++ /dev/null @@ -1,724 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test the quantum amplitude estimation algorithm.""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -import numpy as np -from ddt import ddt, idata, data, unpack -from qiskit import QuantumRegister, QuantumCircuit, BasicAer -from qiskit.circuit.library import QFT, GroverOperator -from qiskit.utils import QuantumInstance -from qiskit.algorithms import ( - AmplitudeEstimation, - MaximumLikelihoodAmplitudeEstimation, - IterativeAmplitudeEstimation, - FasterAmplitudeEstimation, - EstimationProblem, -) -from qiskit.quantum_info import Operator, Statevector -from qiskit.primitives import Sampler - - -class BernoulliStateIn(QuantumCircuit): - """A circuit preparing sqrt(1 - p)|0> + sqrt(p)|1>.""" - - def __init__(self, probability): - super().__init__(1) - angle = 2 * np.arcsin(np.sqrt(probability)) - self.ry(angle, 0) - - -class BernoulliGrover(QuantumCircuit): - """The Grover operator corresponding to the Bernoulli A operator.""" - - def __init__(self, probability): - super().__init__(1, global_phase=np.pi) - self.angle = 2 * np.arcsin(np.sqrt(probability)) - self.ry(2 * self.angle, 0) - - def power(self, power, matrix_power=False): - if matrix_power: - return super().power(power, True) - - powered = QuantumCircuit(1) - powered.ry(power * 2 * self.angle, 0) - return powered - - -class SineIntegral(QuantumCircuit): - r"""Construct the A operator to approximate the integral - - \int_0^1 \sin^2(x) d x - - with a specified number of qubits. - """ - - def __init__(self, num_qubits): - qr_state = QuantumRegister(num_qubits, "state") - qr_objective = QuantumRegister(1, "obj") - super().__init__(qr_state, qr_objective) - - # prepare 1/sqrt{2^n} sum_x |x>_n - self.h(qr_state) - - # apply the sine/cosine term - self.ry(2 * 1 / 2 / 2**num_qubits, qr_objective[0]) - for i, qubit in enumerate(qr_state): - self.cry(2 * 2**i / 2**num_qubits, qubit, qr_objective[0]) - - -@ddt -class TestBernoulli(QiskitAlgorithmsTestCase): - """Tests based on the Bernoulli A operator. - - This class tests - * the estimation result - * the constructed circuits - """ - - def setUp(self): - super().setUp() - - with self.assertWarns(DeprecationWarning): - self._statevector = QuantumInstance( - backend=BasicAer.get_backend("statevector_simulator"), - seed_simulator=2, - seed_transpiler=2, - ) - - self._sampler = Sampler(options={"seed": 2}) - - def qasm(shots=100): - with self.assertWarns(DeprecationWarning): - qi = QuantumInstance( - backend=BasicAer.get_backend("qasm_simulator"), - shots=shots, - seed_simulator=2, - seed_transpiler=2, - ) - return qi - - self._qasm = qasm - - def sampler_shots(shots=100): - return Sampler(options={"shots": shots, "seed": 2}) - - self._sampler_shots = sampler_shots - - @idata( - [ - [0.2, AmplitudeEstimation(2), {"estimation": 0.5, "mle": 0.2}], - [0.49, AmplitudeEstimation(3), {"estimation": 0.5, "mle": 0.49}], - [0.2, MaximumLikelihoodAmplitudeEstimation([0, 1, 2]), {"estimation": 0.2}], - [0.49, MaximumLikelihoodAmplitudeEstimation(3), {"estimation": 0.49}], - [0.2, IterativeAmplitudeEstimation(0.1, 0.1), {"estimation": 0.2}], - [0.49, IterativeAmplitudeEstimation(0.001, 0.01), {"estimation": 0.49}], - [0.2, FasterAmplitudeEstimation(0.1, 3, rescale=False), {"estimation": 0.2}], - [0.12, FasterAmplitudeEstimation(0.1, 2, rescale=False), {"estimation": 0.12}], - ] - ) - @unpack - def test_statevector(self, prob, qae, expect): - """statevector test""" - - problem = EstimationProblem(BernoulliStateIn(prob), 0, BernoulliGrover(prob)) - - with self.assertWarns(DeprecationWarning): - qae.quantum_instance = self._statevector - result = qae.estimate(problem) - - self._statevector.reset_execution_results() - for key, value in expect.items(): - self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" - ) - - @idata( - [ - [0.2, AmplitudeEstimation(2), {"estimation": 0.5, "mle": 0.2}], - [0.49, AmplitudeEstimation(3), {"estimation": 0.5, "mle": 0.49}], - [0.2, MaximumLikelihoodAmplitudeEstimation([0, 1, 2]), {"estimation": 0.2}], - [0.49, MaximumLikelihoodAmplitudeEstimation(3), {"estimation": 0.49}], - [0.2, IterativeAmplitudeEstimation(0.1, 0.1), {"estimation": 0.2}], - [0.49, IterativeAmplitudeEstimation(0.001, 0.01), {"estimation": 0.49}], - [0.2, FasterAmplitudeEstimation(0.1, 3, rescale=False), {"estimation": 0.199}], - [0.12, FasterAmplitudeEstimation(0.1, 2, rescale=False), {"estimation": 0.12}], - ] - ) - @unpack - def test_sampler(self, prob, qae, expect): - """sampler test""" - qae.sampler = self._sampler - problem = EstimationProblem(BernoulliStateIn(prob), 0, BernoulliGrover(prob)) - - result = qae.estimate(problem) - for key, value in expect.items(): - self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" - ) - - @idata( - [ - [0.2, 100, AmplitudeEstimation(4), {"estimation": 0.14644, "mle": 0.193888}], - [0.0, 1000, AmplitudeEstimation(2), {"estimation": 0.0, "mle": 0.0}], - [ - 0.2, - 100, - MaximumLikelihoodAmplitudeEstimation([0, 1, 2, 4, 8]), - {"estimation": 0.199606}, - ], - [0.8, 10, IterativeAmplitudeEstimation(0.1, 0.05), {"estimation": 0.811711}], - [0.2, 1000, FasterAmplitudeEstimation(0.1, 3, rescale=False), {"estimation": 0.198640}], - [ - 0.12, - 100, - FasterAmplitudeEstimation(0.01, 3, rescale=False), - {"estimation": 0.119037}, - ], - ] - ) - @unpack - def test_qasm(self, prob, shots, qae, expect): - """qasm test""" - - with self.assertWarns(DeprecationWarning): - qae.quantum_instance = self._qasm(shots) - problem = EstimationProblem(BernoulliStateIn(prob), [0], BernoulliGrover(prob)) - result = qae.estimate(problem) - - for key, value in expect.items(): - self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" - ) - - @idata( - [ - [0.2, 100, AmplitudeEstimation(4), {"estimation": 0.14644, "mle": 0.198783}], - [0.0, 1000, AmplitudeEstimation(2), {"estimation": 0.0, "mle": 0.0}], - [ - 0.2, - 100, - MaximumLikelihoodAmplitudeEstimation([0, 1, 2, 4, 8]), - {"estimation": 0.200308}, - ], - [0.8, 10, IterativeAmplitudeEstimation(0.1, 0.05), {"estimation": 0.811711}], - [0.2, 1000, FasterAmplitudeEstimation(0.1, 3, rescale=False), {"estimation": 0.198640}], - [ - 0.12, - 100, - FasterAmplitudeEstimation(0.01, 3, rescale=False), - {"estimation": 0.120017}, - ], - ] - ) - @unpack - def test_sampler_with_shots(self, prob, shots, qae, expect): - """sampler with shots test""" - qae.sampler = self._sampler_shots(shots) - problem = EstimationProblem(BernoulliStateIn(prob), [0], BernoulliGrover(prob)) - - result = qae.estimate(problem) - for key, value in expect.items(): - self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" - ) - - @data(True, False) - def test_qae_circuit(self, efficient_circuit): - """Test circuits resulting from canonical amplitude estimation. - - Build the circuit manually and from the algorithm and compare the resulting unitaries. - """ - prob = 0.5 - - problem = EstimationProblem(BernoulliStateIn(prob), objective_qubits=[0]) - for m in [2, 5]: - qae = AmplitudeEstimation(m) - angle = 2 * np.arcsin(np.sqrt(prob)) - - # manually set up the inefficient AE circuit - qr_eval = QuantumRegister(m, "a") - qr_objective = QuantumRegister(1, "q") - circuit = QuantumCircuit(qr_eval, qr_objective) - - # initial Hadamard gates - for i in range(m): - circuit.h(qr_eval[i]) - - # A operator - circuit.ry(angle, qr_objective) - - if efficient_circuit: - qae.grover_operator = BernoulliGrover(prob) - for power in range(m): - circuit.cry(2 * 2**power * angle, qr_eval[power], qr_objective[0]) - else: - oracle = QuantumCircuit(1) - oracle.z(0) - - state_preparation = QuantumCircuit(1) - state_preparation.ry(angle, 0) - grover_op = GroverOperator(oracle, state_preparation) - for power in range(m): - circuit.compose( - grover_op.power(2**power).control(), - qubits=[qr_eval[power], qr_objective[0]], - inplace=True, - ) - - # fourier transform - iqft = QFT(m, do_swaps=False).inverse().reverse_bits() - circuit.append(iqft.to_instruction(), qr_eval) - - actual_circuit = qae.construct_circuit(problem, measurement=False) - - self.assertEqual(Operator(circuit), Operator(actual_circuit)) - - @data(True, False) - def test_iqae_circuits(self, efficient_circuit): - """Test circuits resulting from iterative amplitude estimation. - - Build the circuit manually and from the algorithm and compare the resulting unitaries. - """ - prob = 0.5 - problem = EstimationProblem(BernoulliStateIn(prob), objective_qubits=[0]) - - for k in [2, 5]: - qae = IterativeAmplitudeEstimation(0.01, 0.05) - angle = 2 * np.arcsin(np.sqrt(prob)) - - # manually set up the inefficient AE circuit - q_objective = QuantumRegister(1, "q") - circuit = QuantumCircuit(q_objective) - - # A operator - circuit.ry(angle, q_objective) - - if efficient_circuit: - qae.grover_operator = BernoulliGrover(prob) - circuit.ry(2 * k * angle, q_objective[0]) - - else: - oracle = QuantumCircuit(1) - oracle.z(0) - state_preparation = QuantumCircuit(1) - state_preparation.ry(angle, 0) - grover_op = GroverOperator(oracle, state_preparation) - for _ in range(k): - circuit.compose(grover_op, inplace=True) - - actual_circuit = qae.construct_circuit(problem, k, measurement=False) - self.assertEqual(Operator(circuit), Operator(actual_circuit)) - - @data(True, False) - def test_mlae_circuits(self, efficient_circuit): - """Test the circuits constructed for MLAE""" - prob = 0.5 - problem = EstimationProblem(BernoulliStateIn(prob), objective_qubits=[0]) - - for k in [2, 5]: - qae = MaximumLikelihoodAmplitudeEstimation(k) - angle = 2 * np.arcsin(np.sqrt(prob)) - - # compute all the circuits used for MLAE - circuits = [] - - # 0th power - q_objective = QuantumRegister(1, "q") - circuit = QuantumCircuit(q_objective) - circuit.ry(angle, q_objective) - circuits += [circuit] - - # powers of 2 - for power in range(k): - q_objective = QuantumRegister(1, "q") - circuit = QuantumCircuit(q_objective) - - # A operator - circuit.ry(angle, q_objective) - - # Q^(2^j) operator - if efficient_circuit: - qae.grover_operator = BernoulliGrover(prob) - circuit.ry(2 * 2**power * angle, q_objective[0]) - - else: - oracle = QuantumCircuit(1) - oracle.z(0) - state_preparation = QuantumCircuit(1) - state_preparation.ry(angle, 0) - grover_op = GroverOperator(oracle, state_preparation) - for _ in range(2**power): - circuit.compose(grover_op, inplace=True) - circuits += [circuit] - - actual_circuits = qae.construct_circuits(problem, measurement=False) - - for actual, expected in zip(actual_circuits, circuits): - self.assertEqual(Operator(actual), Operator(expected)) - - -@ddt -class TestSineIntegral(QiskitAlgorithmsTestCase): - """Tests based on the A operator to integrate sin^2(x). - - This class tests - * the estimation result - * the confidence intervals - """ - - def setUp(self): - super().setUp() - - with self.assertWarns(DeprecationWarning): - self._statevector = QuantumInstance( - backend=BasicAer.get_backend("statevector_simulator"), - seed_simulator=123, - seed_transpiler=41, - ) - - self._sampler = Sampler(options={"seed": 123}) - - def qasm(shots=100): - with self.assertWarns(DeprecationWarning): - qi = QuantumInstance( - backend=BasicAer.get_backend("qasm_simulator"), - shots=shots, - seed_simulator=7192, - seed_transpiler=90000, - ) - return qi - - self._qasm = qasm - - def sampler_shots(shots=100): - return Sampler(options={"shots": shots, "seed": 7192}) - - self._sampler_shots = sampler_shots - - @idata( - [ - [2, AmplitudeEstimation(2), {"estimation": 0.5, "mle": 0.270290}], - [4, MaximumLikelihoodAmplitudeEstimation(4), {"estimation": 0.272675}], - [3, IterativeAmplitudeEstimation(0.1, 0.1), {"estimation": 0.272082}], - [3, FasterAmplitudeEstimation(0.01, 1), {"estimation": 0.272082}], - ] - ) - @unpack - def test_statevector(self, n, qae, expect): - """Statevector end-to-end test""" - # construct factories for A and Q - # qae.state_preparation = SineIntegral(n) - estimation_problem = EstimationProblem(SineIntegral(n), objective_qubits=[n]) - - with self.assertWarns(DeprecationWarning): - qae.quantum_instance = self._statevector - # result = qae.run(self._statevector) - result = qae.estimate(estimation_problem) - - self._statevector.reset_execution_results() - for key, value in expect.items(): - self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" - ) - - @idata( - [ - [2, AmplitudeEstimation(2), {"estimation": 0.5, "mle": 0.2702}], - [4, MaximumLikelihoodAmplitudeEstimation(4), {"estimation": 0.2725}], - [3, IterativeAmplitudeEstimation(0.1, 0.1), {"estimation": 0.2721}], - [3, FasterAmplitudeEstimation(0.01, 1), {"estimation": 0.2792}], - ] - ) - @unpack - def test_sampler(self, n, qae, expect): - """sampler end-to-end test""" - # construct factories for A and Q - # qae.state_preparation = SineIntegral(n) - qae.sampler = self._sampler - estimation_problem = EstimationProblem(SineIntegral(n), objective_qubits=[n]) - - result = qae.estimate(estimation_problem) - for key, value in expect.items(): - self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" - ) - - @idata( - [ - [4, 100, AmplitudeEstimation(2), {"estimation": 0.5, "mle": 0.281196}], - [3, 10, MaximumLikelihoodAmplitudeEstimation(2), {"estimation": 0.256878}], - [3, 1000, IterativeAmplitudeEstimation(0.01, 0.01), {"estimation": 0.271790}], - [3, 1000, FasterAmplitudeEstimation(0.1, 4), {"estimation": 0.274168}], - ] - ) - @unpack - def test_qasm(self, n, shots, qae, expect): - """QASM simulator end-to-end test.""" - estimation_problem = EstimationProblem(SineIntegral(n), objective_qubits=[n]) - - with self.assertWarns(DeprecationWarning): - qae.quantum_instance = self._qasm(shots) - result = qae.estimate(estimation_problem) - - for key, value in expect.items(): - self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" - ) - - @idata( - [ - [4, 1000, AmplitudeEstimation(2), {"estimation": 0.5, "mle": 0.2636}], - [3, 10, MaximumLikelihoodAmplitudeEstimation(2), {"estimation": 0.2904}], - [3, 1000, IterativeAmplitudeEstimation(0.01, 0.01), {"estimation": 0.2706}], - [3, 1000, FasterAmplitudeEstimation(0.1, 4), {"estimation": 0.2764}], - ] - ) - @unpack - def test_sampler_with_shots(self, n, shots, qae, expect): - """Sampler with shots end-to-end test.""" - # construct factories for A and Q - qae.sampler = self._sampler_shots(shots) - estimation_problem = EstimationProblem(SineIntegral(n), objective_qubits=[n]) - - result = qae.estimate(estimation_problem) - for key, value in expect.items(): - self.assertAlmostEqual( - value, getattr(result, key), places=3, msg=f"estimate `{key}` failed" - ) - - @idata( - [ - [ - AmplitudeEstimation(3), - "mle", - { - "likelihood_ratio": (0.2494734, 0.3003771), - "fisher": (0.2486176, 0.2999286), - "observed_fisher": (0.2484562, 0.3000900), - }, - ], - [ - MaximumLikelihoodAmplitudeEstimation(3), - "estimation", - { - "likelihood_ratio": (0.2598794, 0.2798536), - "fisher": (0.2584889, 0.2797018), - "observed_fisher": (0.2659279, 0.2722627), - }, - ], - ] - ) - @unpack - def test_confidence_intervals(self, qae, key, expect): - """End-to-end test for all confidence intervals.""" - n = 3 - - estimation_problem = EstimationProblem(SineIntegral(n), objective_qubits=[n]) - with self.assertWarns(DeprecationWarning): - qae.quantum_instance = self._statevector - # statevector simulator - result = qae.estimate(estimation_problem) - - self._statevector.reset_execution_results() - methods = ["lr", "fi", "oi"] # short for likelihood_ratio, fisher, observed_fisher - alphas = [0.1, 0.00001, 0.9] # alpha shouldn't matter in statevector - for alpha, method in zip(alphas, methods): - confint = qae.compute_confidence_interval(result, alpha, method) - # confidence interval based on statevector should be empty, as we are sure of the result - self.assertAlmostEqual(confint[1] - confint[0], 0.0) - self.assertAlmostEqual(confint[0], getattr(result, key)) - - # qasm simulator - shots = 100 - alpha = 0.01 - with self.assertWarns(DeprecationWarning): - qae.quantum_instance = self._qasm(shots) - result = qae.estimate(estimation_problem) - - for method, expected_confint in expect.items(): - confint = qae.compute_confidence_interval(result, alpha, method) - np.testing.assert_array_almost_equal(confint, expected_confint) - self.assertTrue(confint[0] <= getattr(result, key) <= confint[1]) - - def test_iqae_confidence_intervals(self): - """End-to-end test for the IQAE confidence interval.""" - n = 3 - expected_confint = (0.1984050, 0.3511015) - estimation_problem = EstimationProblem(SineIntegral(n), objective_qubits=[n]) - - with self.assertWarns(DeprecationWarning): - qae = IterativeAmplitudeEstimation(0.1, 0.01, quantum_instance=self._statevector) - # statevector simulator - result = qae.estimate(estimation_problem) - - self._statevector.reset_execution_results() - confint = result.confidence_interval - # confidence interval based on statevector should be empty, as we are sure of the result - self.assertAlmostEqual(confint[1] - confint[0], 0.0) - self.assertAlmostEqual(confint[0], result.estimation) - - # qasm simulator - shots = 100 - - with self.assertWarns(DeprecationWarning): - qae.quantum_instance = self._qasm(shots) - result = qae.estimate(estimation_problem) - - confint = result.confidence_interval - np.testing.assert_array_almost_equal(confint, expected_confint) - self.assertTrue(confint[0] <= result.estimation <= confint[1]) - - -class TestAmplitudeEstimation(QiskitAlgorithmsTestCase): - """Specific tests for canonical AE.""" - - def test_warns_if_good_state_set(self): - """Check AE warns if is_good_state is set.""" - circuit = QuantumCircuit(1) - problem = EstimationProblem(circuit, objective_qubits=[0], is_good_state=lambda x: True) - - qae = AmplitudeEstimation(num_eval_qubits=1, sampler=Sampler()) - - with self.assertWarns(Warning): - _ = qae.estimate(problem) - - -@ddt -class TestFasterAmplitudeEstimation(QiskitAlgorithmsTestCase): - """Specific tests for Faster AE.""" - - def setUp(self): - super().setUp() - self._sampler = Sampler(options={"seed": 2}) - - def test_rescaling(self): - """Test the rescaling.""" - amplitude = 0.8 - scaling = 0.25 - circuit = QuantumCircuit(1) - circuit.ry(2 * np.arcsin(amplitude), 0) - problem = EstimationProblem(circuit, objective_qubits=[0]) - - rescaled = problem.rescale(scaling) - rescaled_amplitude = Statevector.from_instruction(rescaled.state_preparation).data[3] - - self.assertAlmostEqual(scaling * amplitude, rescaled_amplitude) - - def test_run_without_rescaling(self): - """Run Faster AE without rescaling if the amplitude is in [0, 1/4].""" - # construct estimation problem - prob = 0.11 - a_op = QuantumCircuit(1) - a_op.ry(2 * np.arcsin(np.sqrt(prob)), 0) - problem = EstimationProblem(a_op, objective_qubits=[0]) - - # construct algo without rescaling - backend = BasicAer.get_backend("statevector_simulator") - - with self.assertWarns(DeprecationWarning): - fae = FasterAmplitudeEstimation(0.1, 1, rescale=False, quantum_instance=backend) - - # run the algo - result = fae.estimate(problem) - - # assert the result is correct - self.assertAlmostEqual(result.estimation, prob) - - # assert no rescaling was used - theta = np.mean(result.theta_intervals[-1]) - value_without_scaling = np.sin(theta) ** 2 - self.assertAlmostEqual(result.estimation, value_without_scaling) - - def test_sampler_run_without_rescaling(self): - """Run Faster AE without rescaling if the amplitude is in [0, 1/4].""" - # construct estimation problem - prob = 0.11 - a_op = QuantumCircuit(1) - a_op.ry(2 * np.arcsin(np.sqrt(prob)), 0) - problem = EstimationProblem(a_op, objective_qubits=[0]) - - # construct algo without rescaling - fae = FasterAmplitudeEstimation(0.1, 1, rescale=False, sampler=self._sampler) - - # run the algo - result = fae.estimate(problem) - - # assert the result is correct - self.assertAlmostEqual(result.estimation, prob, places=2) - - # assert no rescaling was used - theta = np.mean(result.theta_intervals[-1]) - value_without_scaling = np.sin(theta) ** 2 - self.assertAlmostEqual(result.estimation, value_without_scaling) - - def test_rescaling_with_custom_grover_raises(self): - """Test that the rescaling option fails if a custom Grover operator is used.""" - prob = 0.8 - a_op = BernoulliStateIn(prob) - q_op = BernoulliGrover(prob) - problem = EstimationProblem(a_op, objective_qubits=[0], grover_operator=q_op) - - # construct algo without rescaling - backend = BasicAer.get_backend("statevector_simulator") - with self.assertWarns(DeprecationWarning): - fae = FasterAmplitudeEstimation(0.1, 1, quantum_instance=backend) - - # run the algo - with self.assertWarns(Warning): - _ = fae.estimate(problem) - - @data(("statevector_simulator", 0.2), ("qasm_simulator", 0.199440)) - @unpack - def test_good_state(self, backend_str, expect): - """Test with a good state function.""" - - def is_good_state(bitstr): - return bitstr[1] == "1" - - # construct the estimation problem where the second qubit is ignored - a_op = QuantumCircuit(2) - a_op.ry(2 * np.arcsin(np.sqrt(0.2)), 0) - - # oracle only affects first qubit - oracle = QuantumCircuit(2) - oracle.z(0) - - # reflect only on first qubit - q_op = GroverOperator(oracle, a_op, reflection_qubits=[0]) - - # but we measure both qubits (hence both are objective qubits) - problem = EstimationProblem( - a_op, objective_qubits=[0, 1], grover_operator=q_op, is_good_state=is_good_state - ) - - # construct algo - with self.assertWarns(DeprecationWarning): - backend = QuantumInstance( - BasicAer.get_backend(backend_str), seed_simulator=2, seed_transpiler=2 - ) - # cannot use rescaling with a custom grover operator - - with self.assertWarns(DeprecationWarning): - fae = FasterAmplitudeEstimation(0.01, 5, rescale=False, quantum_instance=backend) - - # run the algo - result = fae.estimate(problem) - - # assert the result is correct - self.assertAlmostEqual(result.estimation, expect, places=5) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_aux_ops_evaluator.py b/test/python/algorithms/test_aux_ops_evaluator.py deleted file mode 100644 index bff0b07c09c9..000000000000 --- a/test/python/algorithms/test_aux_ops_evaluator.py +++ /dev/null @@ -1,197 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""Tests evaluator of auxiliary operators for algorithms.""" - -import unittest -import warnings -from typing import Tuple, Union - -from test.python.algorithms import QiskitAlgorithmsTestCase -import numpy as np -from ddt import ddt, data - -from qiskit.algorithms.list_or_dict import ListOrDict -from qiskit.providers import Backend -from qiskit.quantum_info import Statevector -from qiskit.algorithms import eval_observables -from qiskit import BasicAer, QuantumCircuit -from qiskit.circuit.library import EfficientSU2 -from qiskit.opflow import ( - PauliSumOp, - X, - Z, - I, - ExpectationFactory, - OperatorBase, - ExpectationBase, - StateFn, -) -from qiskit.utils import QuantumInstance, algorithm_globals - - -@ddt -class TestAuxOpsEvaluator(QiskitAlgorithmsTestCase): - """Tests evaluator of auxiliary operators for algorithms.""" - - def setUp(self): - super().setUp() - self.seed = 50 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - with self.assertWarns(DeprecationWarning): - self.h2_op = ( - -1.052373245772859 * (I ^ I) - + 0.39793742484318045 * (I ^ Z) - - 0.39793742484318045 * (Z ^ I) - - 0.01128010425623538 * (Z ^ Z) - + 0.18093119978423156 * (X ^ X) - ) - - self.threshold = 1e-8 - self.backend_names = ["statevector_simulator", "qasm_simulator"] - - def get_exact_expectation(self, ansatz: QuantumCircuit, observables: ListOrDict[OperatorBase]): - """ - Calculates the exact expectation to be used as an expected result for unit tests. - """ - if isinstance(observables, dict): - observables_list = list(observables.values()) - else: - observables_list = observables - - # the exact value is a list of (mean, variance) where we expect 0 variance - exact = [ - (Statevector(ansatz).expectation_value(observable), 0) - for observable in observables_list - ] - - if isinstance(observables, dict): - return dict(zip(observables.keys(), exact)) - - return exact - - def _run_test( - self, - expected_result: ListOrDict[Tuple[complex, complex]], - quantum_state: Union[QuantumCircuit, Statevector], - decimal: int, - expectation: ExpectationBase, - observables: ListOrDict[OperatorBase], - quantum_instance: Union[QuantumInstance, Backend], - ): - - with self.assertWarns(DeprecationWarning): - result = eval_observables( - quantum_instance, quantum_state, observables, expectation, self.threshold - ) - - if isinstance(observables, dict): - np.testing.assert_equal(list(result.keys()), list(expected_result.keys())) - np.testing.assert_array_almost_equal( - list(result.values()), list(expected_result.values()), decimal=decimal - ) - else: - np.testing.assert_array_almost_equal(result, expected_result, decimal=decimal) - - @data( - [ - PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]), - PauliSumOp.from_list([("II", 2.0)]), - ], - [ - PauliSumOp.from_list([("ZZ", 2.0)]), - ], - { - "op1": PauliSumOp.from_list([("II", 2.0)]), - "op2": PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]), - }, - { - "op1": PauliSumOp.from_list([("ZZ", 2.0)]), - }, - ) - def test_eval_observables(self, observables: ListOrDict[OperatorBase]): - """Tests evaluator of auxiliary operators for algorithms.""" - - ansatz = EfficientSU2(2) - parameters = np.array( - [1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0], - dtype=float, - ) - - bound_ansatz = ansatz.assign_parameters(parameters) - expected_result = self.get_exact_expectation(bound_ansatz, observables) - - for backend_name in self.backend_names: - shots = 4096 if backend_name == "qasm_simulator" else 1 - decimal = ( - 1 if backend_name == "qasm_simulator" else 6 - ) # to accommodate for qasm being imperfect - with self.subTest(msg=f"Test {backend_name} backend."): - backend = BasicAer.get_backend(backend_name) - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=backend, - shots=shots, - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - expectation = ExpectationFactory.build( - operator=self.h2_op, - backend=quantum_instance, - ) - - with self.subTest(msg="Test QuantumCircuit."): - self._run_test( - expected_result, - bound_ansatz, - decimal, - expectation, - observables, - quantum_instance, - ) - - with self.subTest(msg="Test QuantumCircuit with Backend."): - self._run_test( - expected_result, - bound_ansatz, - decimal, - expectation, - observables, - backend, - ) - - with self.subTest(msg="Test Statevector."): - statevector = Statevector(bound_ansatz) - self._run_test( - expected_result, - statevector, - decimal, - expectation, - observables, - quantum_instance, - ) - with self.assertWarns(DeprecationWarning): - with self.subTest(msg="Test StateFn."): - statefn = StateFn(bound_ansatz) - self._run_test( - expected_result, - statefn, - decimal, - expectation, - observables, - quantum_instance, - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_backendv1.py b/test/python/algorithms/test_backendv1.py deleted file mode 100644 index d0a544834c72..000000000000 --- a/test/python/algorithms/test_backendv1.py +++ /dev/null @@ -1,148 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test Providers that support BackendV1 interface""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase -from qiskit import QuantumCircuit -from qiskit.providers.fake_provider import FakeProvider -from qiskit.utils import QuantumInstance, algorithm_globals -from qiskit.algorithms import VQE, Grover, AmplificationProblem -from qiskit.opflow import X, Z, I -from qiskit.algorithms.optimizers import SPSA -from qiskit.circuit.library import TwoLocal, EfficientSU2 -from qiskit.utils.mitigation import CompleteMeasFitter - - -class TestBackendV1(QiskitAlgorithmsTestCase): - """test BackendV1 interface""" - - def setUp(self): - super().setUp() - self._provider = FakeProvider() - self._qasm = self._provider.get_backend("fake_qasm_simulator") - self.seed = 50 - - def test_vqe_qasm(self): - """Test the VQE on QASM simulator.""" - optimizer = SPSA(maxiter=300, last_avg=5) - wavefunction = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz") - - with self.assertWarns(DeprecationWarning): - h2_op = ( - -1.052373245772859 * (I ^ I) - + 0.39793742484318045 * (I ^ Z) - - 0.39793742484318045 * (Z ^ I) - - 0.01128010425623538 * (Z ^ Z) - + 0.18093119978423156 * (X ^ X) - ) - qasm_simulator = QuantumInstance( - self._qasm, shots=1024, seed_simulator=self.seed, seed_transpiler=self.seed - ) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - optimizer=optimizer, - max_evals_grouped=1, - quantum_instance=qasm_simulator, - ) - result = vqe.compute_minimum_eigenvalue(operator=h2_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.86, delta=0.05) - - def test_run_circuit_oracle(self): - """Test execution with a quantum circuit oracle""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - problem = AmplificationProblem(oracle, is_good_state=["11"]) - - with self.assertWarns(DeprecationWarning): - qi = QuantumInstance( - self._provider.get_backend("fake_vigo"), seed_simulator=12, seed_transpiler=32 - ) - - with self.assertWarns(DeprecationWarning): - grover = Grover(quantum_instance=qi) - result = grover.amplify(problem) - - self.assertIn(result.top_measurement, ["11"]) - - def test_run_circuit_oracle_single_experiment_backend(self): - """Test execution with a quantum circuit oracle""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - problem = AmplificationProblem(oracle, is_good_state=["11"]) - backend = self._provider.get_backend("fake_vigo") - backend._configuration.max_experiments = 1 - - with self.assertWarns(DeprecationWarning): - qi = QuantumInstance(backend, seed_simulator=12, seed_transpiler=32) - - with self.assertWarns(DeprecationWarning): - grover = Grover(quantum_instance=qi) - result = grover.amplify(problem) - - self.assertIn(result.top_measurement, ["11"]) - - def test_measurement_error_mitigation_with_vqe(self): - """measurement error mitigation test with vqe""" - try: - from qiskit.providers.aer import noise - except ImportError as ex: - self.skipTest(f"Package doesn't appear to be installed. Error: '{str(ex)}'") - return - - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - - # build noise model - noise_model = noise.NoiseModel() - read_err = noise.errors.readout_error.ReadoutError([[0.9, 0.1], [0.25, 0.75]]) - noise_model.add_all_qubit_readout_error(read_err) - - backend = self._qasm - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=backend, - seed_simulator=167, - seed_transpiler=167, - noise_model=noise_model, - measurement_error_mitigation_cls=CompleteMeasFitter, - ) - h2_hamiltonian = ( - -1.052373245772859 * (I ^ I) - + 0.39793742484318045 * (I ^ Z) - - 0.39793742484318045 * (Z ^ I) - - 0.01128010425623538 * (Z ^ Z) - + 0.18093119978423156 * (X ^ X) - ) - - optimizer = SPSA(maxiter=200) - ansatz = EfficientSU2(2, reps=1) - - with self.assertWarns(DeprecationWarning): - vqe = VQE(ansatz=ansatz, optimizer=optimizer, quantum_instance=quantum_instance) - result = vqe.compute_minimum_eigenvalue(operator=h2_hamiltonian) - - self.assertGreater(quantum_instance.time_taken, 0.0) - quantum_instance.reset_execution_results() - self.assertAlmostEqual(result.eigenvalue.real, -1.86, delta=0.05) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_backendv2.py b/test/python/algorithms/test_backendv2.py deleted file mode 100644 index 0987a631f690..000000000000 --- a/test/python/algorithms/test_backendv2.py +++ /dev/null @@ -1,102 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test Providers that support BackendV2 interface""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -from qiskit import QuantumCircuit -from qiskit.providers.fake_provider import FakeProvider -from qiskit.providers.fake_provider.fake_backend_v2 import FakeBackendSimple -from qiskit.utils import QuantumInstance -from qiskit.algorithms import VQE, Grover, AmplificationProblem -from qiskit.opflow import X, Z, I -from qiskit.algorithms.optimizers import SPSA -from qiskit.circuit.library import TwoLocal - - -class TestBackendV2(QiskitAlgorithmsTestCase): - """test BackendV2 interface""" - - def setUp(self): - super().setUp() - self._provider = FakeProvider() - self._qasm = FakeBackendSimple() - self.seed = 50 - - def test_vqe_qasm(self): - """Test the VQE on QASM simulator.""" - optimizer = SPSA(maxiter=300, last_avg=5) - wavefunction = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz") - - with self.assertWarns(DeprecationWarning): - h2_op = ( - -1.052373245772859 * (I ^ I) - + 0.39793742484318045 * (I ^ Z) - - 0.39793742484318045 * (Z ^ I) - - 0.01128010425623538 * (Z ^ Z) - + 0.18093119978423156 * (X ^ X) - ) - qasm_simulator = QuantumInstance( - self._qasm, shots=1024, seed_simulator=self.seed, seed_transpiler=self.seed - ) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - optimizer=optimizer, - max_evals_grouped=1, - quantum_instance=qasm_simulator, - ) - result = vqe.compute_minimum_eigenvalue(operator=h2_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.86, delta=0.05) - - def test_run_circuit_oracle(self): - """Test execution with a quantum circuit oracle""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - problem = AmplificationProblem(oracle, is_good_state=["11"]) - - with self.assertWarns(DeprecationWarning): - qi = QuantumInstance( - self._provider.get_backend("fake_yorktown"), seed_simulator=12, seed_transpiler=32 - ) - - with self.assertWarns(DeprecationWarning): - grover = Grover(quantum_instance=qi) - result = grover.amplify(problem) - - self.assertIn(result.top_measurement, ["11"]) - - def test_run_circuit_oracle_single_experiment_backend(self): - """Test execution with a quantum circuit oracle""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - problem = AmplificationProblem(oracle, is_good_state=["11"]) - backend = self._provider.get_backend("fake_yorktown") - backend._configuration.max_experiments = 1 - - with self.assertWarns(DeprecationWarning): - qi = QuantumInstance( - self._provider.get_backend("fake_yorktown"), seed_simulator=12, seed_transpiler=32 - ) - - with self.assertWarns(DeprecationWarning): - grover = Grover(quantum_instance=qi) - result = grover.amplify(problem) - - self.assertIn(result.top_measurement, ["11"]) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_entangler_map.py b/test/python/algorithms/test_entangler_map.py deleted file mode 100644 index 56dd5100c64f..000000000000 --- a/test/python/algorithms/test_entangler_map.py +++ /dev/null @@ -1,68 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test Entangler Map""" - -import unittest - -from test.python.algorithms import QiskitAlgorithmsTestCase -from qiskit.utils import get_entangler_map, validate_entangler_map - - -class TestEntanglerMap(QiskitAlgorithmsTestCase): - """Test Entangler Map""" - - def test_map_type_linear(self): - """,ap type linear test""" - ref_map = [[0, 1], [1, 2], [2, 3]] - entangler_map = get_entangler_map("linear", 4) - - for (ref_src, ref_targ), (exp_src, exp_targ) in zip(ref_map, entangler_map): - self.assertEqual(ref_src, exp_src) - self.assertEqual(ref_targ, exp_targ) - - def test_map_type_full(self): - """map type full test""" - ref_map = [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]] - entangler_map = get_entangler_map("full", 4) - - for (ref_src, ref_targ), (exp_src, exp_targ) in zip(ref_map, entangler_map): - self.assertEqual(ref_src, exp_src) - self.assertEqual(ref_targ, exp_targ) - - def test_validate_entangler_map(self): - """validate entangler map test""" - valid_map = [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]] - self.assertTrue(validate_entangler_map(valid_map, 4)) - - valid_map_2 = [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3], [3, 2]] - self.assertTrue(validate_entangler_map(valid_map_2, 4, True)) - - invalid_map = [[0, 4], [4, 2], [0, 3], [1, 2], [1, 3], [2, 3]] - with self.assertRaises(ValueError): - validate_entangler_map(invalid_map, 4) - - invalid_map_2 = [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3], [3, 2]] - with self.assertRaises(ValueError): - validate_entangler_map(invalid_map_2, 4) - - wrong_type_map = {0: [1, 2, 3], 1: [2, 3]} - with self.assertRaises(TypeError): - validate_entangler_map(wrong_type_map, 4) - - wrong_type_map_2 = [(0, 1), (0, 2), (0, 3)] - with self.assertRaises(TypeError): - validate_entangler_map(wrong_type_map_2, 4) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_grover.py b/test/python/algorithms/test_grover.py deleted file mode 100644 index 0ba6cbaccf04..000000000000 --- a/test/python/algorithms/test_grover.py +++ /dev/null @@ -1,402 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test Grover's algorithm.""" - -import itertools -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -from ddt import data, ddt, idata, unpack - -from qiskit import BasicAer, QuantumCircuit -from qiskit.algorithms import AmplificationProblem, Grover -from qiskit.circuit.library import GroverOperator, PhaseOracle -from qiskit.primitives import Sampler -from qiskit.quantum_info import Operator, Statevector -from qiskit.utils import QuantumInstance, algorithm_globals -from qiskit.utils.optionals import HAS_TWEEDLEDUM - - -@ddt -class TestAmplificationProblem(QiskitAlgorithmsTestCase): - """Test the amplification problem.""" - - def setUp(self): - super().setUp() - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - self._expected_grover_op = GroverOperator(oracle=oracle) - - @data("oracle_only", "oracle_and_stateprep") - def test_groverop_getter(self, kind): - """Test the default construction of the Grover operator.""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - - if kind == "oracle_only": - problem = AmplificationProblem(oracle, is_good_state=["11"]) - expected = GroverOperator(oracle) - else: - stateprep = QuantumCircuit(2) - stateprep.ry(0.2, [0, 1]) - problem = AmplificationProblem( - oracle, state_preparation=stateprep, is_good_state=["11"] - ) - expected = GroverOperator(oracle, stateprep) - - self.assertEqual(Operator(expected), Operator(problem.grover_operator)) - - @data("list_str", "list_int", "statevector", "callable") - def test_is_good_state(self, kind): - """Test is_good_state works on different input types.""" - if kind == "list_str": - is_good_state = ["01", "11"] - elif kind == "list_int": - is_good_state = [1] # means bitstr[1] == '1' - elif kind == "statevector": - is_good_state = Statevector(np.array([0, 1, 0, 1]) / np.sqrt(2)) - else: - - def is_good_state(bitstr): - # same as ``bitstr in ['01', '11']`` - return bitstr[1] == "1" - - possible_states = [ - "".join(list(map(str, item))) for item in itertools.product([0, 1], repeat=2) - ] - - oracle = QuantumCircuit(2) - problem = AmplificationProblem(oracle, is_good_state=is_good_state) - - expected = [state in ["01", "11"] for state in possible_states] - actual = [problem.is_good_state(state) for state in possible_states] - - self.assertListEqual(expected, actual) - - -@ddt -class TestGrover(QiskitAlgorithmsTestCase): - """Test for the functionality of Grover""" - - def setUp(self): - super().setUp() - with self.assertWarns(DeprecationWarning): - self.statevector = QuantumInstance( - BasicAer.get_backend("statevector_simulator"), seed_simulator=12, seed_transpiler=32 - ) - self.qasm = QuantumInstance( - BasicAer.get_backend("qasm_simulator"), seed_simulator=12, seed_transpiler=32 - ) - self._sampler = Sampler() - self._sampler_with_shots = Sampler(options={"shots": 1024, "seed": 123}) - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 123 - - @unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum required for this test") - @data("ideal", "shots", False) - def test_implicit_phase_oracle_is_good_state(self, use_sampler): - """Test implicit default for is_good_state with PhaseOracle.""" - grover = self._prepare_grover(use_sampler) - oracle = PhaseOracle("x & y") - problem = AmplificationProblem(oracle) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertEqual(result.top_measurement, "11") - - @idata(itertools.product(["ideal", "shots", False], [[1, 2, 3], None, 2])) - @unpack - def test_iterations_with_good_state(self, use_sampler, iterations): - """Test the algorithm with different iteration types and with good state""" - grover = self._prepare_grover(use_sampler, iterations) - problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"]) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertEqual(result.top_measurement, "111") - - @idata(itertools.product(["shots", False], [[1, 2, 3], None, 2])) - @unpack - def test_iterations_with_good_state_sample_from_iterations(self, use_sampler, iterations): - """Test the algorithm with different iteration types and with good state""" - grover = self._prepare_grover(use_sampler, iterations, sample_from_iterations=True) - problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"]) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertEqual(result.top_measurement, "111") - - @data("ideal", "shots", False) - def test_fixed_iterations_without_good_state(self, use_sampler): - """Test the algorithm with iterations as an int and without good state""" - grover = self._prepare_grover(use_sampler, iterations=2) - problem = AmplificationProblem(Statevector.from_label("111")) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertEqual(result.top_measurement, "111") - - @idata(itertools.product(["ideal", "shots", False], [[1, 2, 3], None])) - @unpack - def test_iterations_without_good_state(self, use_sampler, iterations): - """Test the correct error is thrown for none/list of iterations and without good state""" - grover = self._prepare_grover(use_sampler, iterations=iterations) - problem = AmplificationProblem(Statevector.from_label("111")) - - with self.assertRaisesRegex( - TypeError, "An is_good_state function is required with the provided oracle" - ): - if not use_sampler: - with self.assertWarns(DeprecationWarning): - grover.amplify(problem) - else: - grover.amplify(problem) - - @data("ideal", "shots", False) - def test_iterator(self, use_sampler): - """Test running the algorithm on an iterator.""" - - # step-function iterator - def iterator(): - wait, value, count = 3, 1, 0 - while True: - yield value - count += 1 - if count % wait == 0: - value += 1 - - grover = self._prepare_grover(use_sampler, iterations=iterator()) - problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"]) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertEqual(result.top_measurement, "111") - - @data("ideal", "shots", False) - def test_growth_rate(self, use_sampler): - """Test running the algorithm on a growth rate""" - grover = self._prepare_grover(use_sampler, growth_rate=8 / 7) - problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"]) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertEqual(result.top_measurement, "111") - - @data("ideal", "shots", False) - def test_max_num_iterations(self, use_sampler): - """Test the iteration stops when the maximum number of iterations is reached.""" - - def zero(): - while True: - yield 0 - - grover = self._prepare_grover(use_sampler, iterations=zero()) - n = 5 - problem = AmplificationProblem(Statevector.from_label("1" * n), is_good_state=["1" * n]) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertEqual(len(result.iterations), 2**n) - - @data("ideal", "shots", False) - def test_max_power(self, use_sampler): - """Test the iteration stops when the maximum power is reached.""" - lam = 10.0 - grover = self._prepare_grover(use_sampler, growth_rate=lam) - problem = AmplificationProblem(Statevector.from_label("111"), is_good_state=["111"]) - result = grover.amplify(problem) - self.assertEqual(len(result.iterations), 0) - - @data("ideal", "shots", False) - def test_run_circuit_oracle(self, use_sampler): - """Test execution with a quantum circuit oracle""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - problem = AmplificationProblem(oracle, is_good_state=["11"]) - grover = self._prepare_grover(use_sampler) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertIn(result.top_measurement, ["11"]) - - @data("ideal", "shots", False) - def test_run_state_vector_oracle(self, use_sampler): - """Test execution with a state vector oracle""" - mark_state = Statevector.from_label("11") - problem = AmplificationProblem(mark_state, is_good_state=["11"]) - grover = self._prepare_grover(use_sampler) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertIn(result.top_measurement, ["11"]) - - @data("ideal", "shots", False) - def test_run_custom_grover_operator(self, use_sampler): - """Test execution with a grover operator oracle""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - grover_op = GroverOperator(oracle) - problem = AmplificationProblem( - oracle=oracle, grover_operator=grover_op, is_good_state=["11"] - ) - grover = self._prepare_grover(use_sampler) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertIn(result.top_measurement, ["11"]) - - def test_optimal_num_iterations(self): - """Test optimal_num_iterations""" - num_qubits = 7 - for num_solutions in range(1, 2**num_qubits): - amplitude = np.sqrt(num_solutions / 2**num_qubits) - expected = round(np.arccos(amplitude) / (2 * np.arcsin(amplitude))) - actual = Grover.optimal_num_iterations(num_solutions, num_qubits) - self.assertEqual(actual, expected) - - def test_construct_circuit(self): - """Test construct_circuit""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - problem = AmplificationProblem(oracle, is_good_state=["11"]) - grover = Grover() - constructed = grover.construct_circuit(problem, 2, measurement=False) - - grover_op = GroverOperator(oracle) - expected = QuantumCircuit(2) - expected.h([0, 1]) - expected.compose(grover_op.power(2), inplace=True) - - self.assertTrue(Operator(constructed).equiv(Operator(expected))) - - @data("ideal", "shots", False) - def test_circuit_result(self, use_sampler): - """Test circuit_result""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - # is_good_state=['00'] is intentionally selected to obtain a list of results - problem = AmplificationProblem(oracle, is_good_state=["00"]) - grover = self._prepare_grover(use_sampler, iterations=[1, 2, 3, 4]) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - if use_sampler: - for i, dist in enumerate(result.circuit_results): - keys, values = zip(*sorted(dist.items())) - if i in (0, 3): - self.assertTupleEqual(keys, ("11",)) - np.testing.assert_allclose(values, [1], atol=0.2) - else: - self.assertTupleEqual(keys, ("00", "01", "10", "11")) - np.testing.assert_allclose(values, [0.25, 0.25, 0.25, 0.25], atol=0.2) - else: - expected_results = [ - {"11": 1024}, - {"00": 238, "01": 253, "10": 263, "11": 270}, - {"00": 238, "01": 253, "10": 263, "11": 270}, - {"11": 1024}, - ] - self.assertEqual(result.circuit_results, expected_results) - - @data("ideal", "shots", False) - def test_max_probability(self, use_sampler): - """Test max_probability""" - oracle = QuantumCircuit(2) - oracle.cz(0, 1) - problem = AmplificationProblem(oracle, is_good_state=["11"]) - grover = self._prepare_grover(use_sampler) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertAlmostEqual(result.max_probability, 1.0) - - @unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum required for this test") - @data("ideal", "shots", False) - def test_oracle_evaluation(self, use_sampler): - """Test oracle_evaluation for PhaseOracle""" - oracle = PhaseOracle("x1 & x2 & (not x3)") - problem = AmplificationProblem(oracle, is_good_state=oracle.evaluate_bitstring) - grover = self._prepare_grover(use_sampler) - if not use_sampler: - with self.assertWarns(DeprecationWarning): - result = grover.amplify(problem) - else: - result = grover.amplify(problem) - self.assertTrue(result.oracle_evaluation) - self.assertEqual("011", result.top_measurement) - - def test_sampler_setter(self): - """Test sampler setter""" - grover = Grover() - grover.sampler = self._sampler - self.assertEqual(grover.sampler, self._sampler) - - def _prepare_grover( - self, use_sampler, iterations=None, growth_rate=None, sample_from_iterations=False - ): - """Prepare Grover instance for test""" - if use_sampler == "ideal": - grover = Grover( - sampler=self._sampler, - iterations=iterations, - growth_rate=growth_rate, - sample_from_iterations=sample_from_iterations, - ) - elif use_sampler == "shots": - grover = Grover( - sampler=self._sampler_with_shots, - iterations=iterations, - growth_rate=growth_rate, - sample_from_iterations=sample_from_iterations, - ) - else: - with self.assertWarns(DeprecationWarning): - grover = Grover( - quantum_instance=self.qasm, - iterations=iterations, - growth_rate=growth_rate, - sample_from_iterations=sample_from_iterations, - ) - return grover - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_measure_error_mitigation.py b/test/python/algorithms/test_measure_error_mitigation.py deleted file mode 100644 index ed9e972c524e..000000000000 --- a/test/python/algorithms/test_measure_error_mitigation.py +++ /dev/null @@ -1,524 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test Measurement Error Mitigation""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import ddt, data, unpack -import numpy as np -import rustworkx as rx -from qiskit import QuantumCircuit, execute -from qiskit.quantum_info import Pauli -from qiskit.exceptions import QiskitError -from qiskit.utils import QuantumInstance, algorithm_globals -from qiskit.algorithms import VQE, QAOA -from qiskit.opflow import I, X, Z, PauliSumOp -from qiskit.algorithms.optimizers import SPSA, COBYLA -from qiskit.circuit.library import EfficientSU2 -from qiskit.utils.mitigation import CompleteMeasFitter, TensoredMeasFitter -from qiskit.utils.measurement_error_mitigation import build_measurement_error_mitigation_circuits -from qiskit.utils import optionals - -if optionals.HAS_AER: - # pylint: disable=no-name-in-module - from qiskit import Aer - from qiskit.providers.aer import noise -if optionals.HAS_IGNIS: - # pylint: disable=no-name-in-module - from qiskit.ignis.mitigation.measurement import ( - CompleteMeasFitter as CompleteMeasFitter_IG, - TensoredMeasFitter as TensoredMeasFitter_IG, - ) - - -@ddt -class TestMeasurementErrorMitigation(QiskitAlgorithmsTestCase): - """Test measurement error mitigation.""" - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required for this test") - @data( - ("CompleteMeasFitter", None, False), - ("TensoredMeasFitter", None, False), - ("TensoredMeasFitter", [[0, 1]], True), - ("TensoredMeasFitter", [[1], [0]], False), - ) - @unpack - def test_measurement_error_mitigation_with_diff_qubit_order( - self, - fitter_str, - mit_pattern, - fails, - ): - """measurement error mitigation with different qubit order""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - - # build noise model - noise_model = noise.NoiseModel() - read_err = noise.errors.readout_error.ReadoutError([[0.9, 0.1], [0.25, 0.75]]) - noise_model.add_all_qubit_readout_error(read_err) - - fitter_cls = ( - CompleteMeasFitter if fitter_str == "CompleteMeasFitter" else TensoredMeasFitter - ) - backend = Aer.get_backend("aer_simulator") - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=backend, - seed_simulator=1679, - seed_transpiler=167, - shots=1000, - noise_model=noise_model, - measurement_error_mitigation_cls=fitter_cls, - cals_matrix_refresh_period=0, - mit_pattern=mit_pattern, - ) - - # circuit - qc1 = QuantumCircuit(2, 2) - qc1.h(0) - qc1.cx(0, 1) - qc1.measure(0, 0) - qc1.measure(1, 1) - qc2 = QuantumCircuit(2, 2) - qc2.h(0) - qc2.cx(0, 1) - qc2.measure(1, 0) - qc2.measure(0, 1) - - with self.assertWarns(DeprecationWarning): - if fails: - self.assertRaisesRegex( - QiskitError, - "Each element in the mit pattern should have length 1.", - quantum_instance.execute, - [qc1, qc2], - ) - else: - quantum_instance.execute([qc1, qc2]) - - self.assertGreater(quantum_instance.time_taken, 0.0) - quantum_instance.reset_execution_results() - - # failure case - qc3 = QuantumCircuit(3, 3) - qc3.h(2) - qc3.cx(1, 2) - qc3.measure(2, 1) - qc3.measure(1, 2) - - with self.assertWarns(DeprecationWarning): - self.assertRaises(QiskitError, quantum_instance.execute, [qc1, qc3]) - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required for this test") - @data(("CompleteMeasFitter", None), ("TensoredMeasFitter", [[0], [1]])) - def test_measurement_error_mitigation_with_vqe(self, config): - """measurement error mitigation test with vqe""" - - fitter_str, mit_pattern = config - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - - # build noise model - noise_model = noise.NoiseModel() - read_err = noise.errors.readout_error.ReadoutError([[0.9, 0.1], [0.25, 0.75]]) - noise_model.add_all_qubit_readout_error(read_err) - - fitter_cls = ( - CompleteMeasFitter if fitter_str == "CompleteMeasFitter" else TensoredMeasFitter - ) - backend = Aer.get_backend("aer_simulator") - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=backend, - seed_simulator=167, - seed_transpiler=167, - noise_model=noise_model, - measurement_error_mitigation_cls=fitter_cls, - mit_pattern=mit_pattern, - ) - - optimizer = SPSA(maxiter=200) - ansatz = EfficientSU2(2, reps=1) - - with self.assertWarns(DeprecationWarning): - h2_hamiltonian = ( - -1.052373245772859 * (I ^ I) - + 0.39793742484318045 * (I ^ Z) - - 0.39793742484318045 * (Z ^ I) - - 0.01128010425623538 * (Z ^ Z) - + 0.18093119978423156 * (X ^ X) - ) - - with self.assertWarns(DeprecationWarning): - vqe = VQE(ansatz=ansatz, optimizer=optimizer, quantum_instance=quantum_instance) - result = vqe.compute_minimum_eigenvalue(operator=h2_hamiltonian) - - self.assertGreater(quantum_instance.time_taken, 0.0) - quantum_instance.reset_execution_results() - self.assertAlmostEqual(result.eigenvalue.real, -1.86, delta=0.05) - - def _get_operator(self, weight_matrix): - """Generate Hamiltonian for the max-cut problem of a graph. - - Args: - weight_matrix (numpy.ndarray) : adjacency matrix. - - Returns: - PauliSumOp: operator for the Hamiltonian - float: a constant shift for the obj function. - - """ - num_nodes = weight_matrix.shape[0] - pauli_list = [] - shift = 0 - for i in range(num_nodes): - for j in range(i): - if weight_matrix[i, j] != 0: - x_p = np.zeros(num_nodes, dtype=bool) - z_p = np.zeros(num_nodes, dtype=bool) - z_p[i] = True - z_p[j] = True - pauli_list.append([0.5 * weight_matrix[i, j], Pauli((z_p, x_p))]) - shift -= 0.5 * weight_matrix[i, j] - opflow_list = [(pauli[1].to_label(), pauli[0]) for pauli in pauli_list] - - with self.assertWarns(DeprecationWarning): - return PauliSumOp.from_list(opflow_list), shift - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required for this test") - def test_measurement_error_mitigation_qaoa(self): - """measurement error mitigation test with QAOA""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 167 - - backend = Aer.get_backend("aer_simulator") - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - w = rx.adjacency_matrix( - rx.undirected_gnp_random_graph(5, 0.5, seed=algorithm_globals.random_seed) - ) - qubit_op, _ = self._get_operator(w) - initial_point = np.asarray([0.0, 0.0]) - - # Compute first without noise - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=backend, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - - with self.assertWarns(DeprecationWarning): - qaoa = QAOA( - optimizer=COBYLA(maxiter=3), - quantum_instance=quantum_instance, - initial_point=initial_point, - ) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - ref_eigenvalue = result.eigenvalue.real - - # compute with noise - # build noise model - noise_model = noise.NoiseModel() - read_err = noise.errors.readout_error.ReadoutError([[0.9, 0.1], [0.25, 0.75]]) - noise_model.add_all_qubit_readout_error(read_err) - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=backend, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - noise_model=noise_model, - measurement_error_mitigation_cls=CompleteMeasFitter, - shots=10000, - ) - - with self.assertWarns(DeprecationWarning): - - qaoa = QAOA( - optimizer=COBYLA(maxiter=3), - quantum_instance=quantum_instance, - initial_point=initial_point, - ) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - self.assertAlmostEqual(result.eigenvalue.real, ref_eigenvalue, delta=0.05) - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required for this test") - @unittest.skipUnless(optionals.HAS_IGNIS, "qiskit-ignis is required to run this test") - @data("CompleteMeasFitter", "TensoredMeasFitter") - def test_measurement_error_mitigation_with_diff_qubit_order_ignis(self, fitter_str): - """measurement error mitigation with different qubit order""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - - # build noise model - noise_model = noise.NoiseModel() - read_err = noise.errors.readout_error.ReadoutError([[0.9, 0.1], [0.25, 0.75]]) - noise_model.add_all_qubit_readout_error(read_err) - - fitter_cls = ( - CompleteMeasFitter_IG if fitter_str == "CompleteMeasFitter" else TensoredMeasFitter_IG - ) - backend = Aer.get_backend("aer_simulator") - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=backend, - seed_simulator=1679, - seed_transpiler=167, - shots=1000, - noise_model=noise_model, - measurement_error_mitigation_cls=fitter_cls, - cals_matrix_refresh_period=0, - ) - - # circuit - qc1 = QuantumCircuit(2, 2) - qc1.h(0) - qc1.cx(0, 1) - qc1.measure(0, 0) - qc1.measure(1, 1) - qc2 = QuantumCircuit(2, 2) - qc2.h(0) - qc2.cx(0, 1) - qc2.measure(1, 0) - qc2.measure(0, 1) - - if fitter_cls == TensoredMeasFitter_IG: - with self.assertWarnsRegex(DeprecationWarning, r".*ignis.*"): - self.assertRaisesRegex( - QiskitError, - "TensoredMeasFitter doesn't support subset_fitter.", - quantum_instance.execute, - [qc1, qc2], - ) - else: - # this should run smoothly - with self.assertWarnsRegex(DeprecationWarning, r".*ignis.*"): - quantum_instance.execute([qc1, qc2]) - - self.assertGreater(quantum_instance.time_taken, 0.0) - quantum_instance.reset_execution_results() - - # failure case - qc3 = QuantumCircuit(3, 3) - qc3.h(2) - qc3.cx(1, 2) - qc3.measure(2, 1) - qc3.measure(1, 2) - - self.assertRaises(QiskitError, quantum_instance.execute, [qc1, qc3]) - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required for this test") - @unittest.skipUnless(optionals.HAS_IGNIS, "qiskit-ignis is required to run this test") - @data(("CompleteMeasFitter", None), ("TensoredMeasFitter", [[0], [1]])) - def test_measurement_error_mitigation_with_vqe_ignis(self, config): - """measurement error mitigation test with vqe""" - fitter_str, mit_pattern = config - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - - # build noise model - noise_model = noise.NoiseModel() - read_err = noise.errors.readout_error.ReadoutError([[0.9, 0.1], [0.25, 0.75]]) - noise_model.add_all_qubit_readout_error(read_err) - - fitter_cls = ( - CompleteMeasFitter_IG if fitter_str == "CompleteMeasFitter" else TensoredMeasFitter_IG - ) - backend = Aer.get_backend("aer_simulator") - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=backend, - seed_simulator=167, - seed_transpiler=167, - noise_model=noise_model, - measurement_error_mitigation_cls=fitter_cls, - mit_pattern=mit_pattern, - ) - - h2_hamiltonian = ( - -1.052373245772859 * (I ^ I) - + 0.39793742484318045 * (I ^ Z) - - 0.39793742484318045 * (Z ^ I) - - 0.01128010425623538 * (Z ^ Z) - + 0.18093119978423156 * (X ^ X) - ) - optimizer = SPSA(maxiter=200) - ansatz = EfficientSU2(2, reps=1) - - with self.assertWarnsRegex(DeprecationWarning): - vqe = VQE(ansatz=ansatz, optimizer=optimizer, quantum_instance=quantum_instance) - result = vqe.compute_minimum_eigenvalue(operator=h2_hamiltonian) - - self.assertGreater(quantum_instance.time_taken, 0.0) - quantum_instance.reset_execution_results() - self.assertAlmostEqual(result.eigenvalue.real, -1.86, delta=0.05) - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required for this test") - @unittest.skipUnless(optionals.HAS_IGNIS, "qiskit-ignis is required to run this test") - def test_calibration_results(self): - """check that results counts are the same with/without error mitigation""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 1679 - np.random.seed(algorithm_globals.random_seed) - - qc = QuantumCircuit(1) - qc.x(0) - - qc_meas = qc.copy() - qc_meas.measure_all() - backend = Aer.get_backend("aer_simulator") - - counts_array = [None, None] - for idx, is_use_mitigation in enumerate([True, False]): - with self.assertWarns(DeprecationWarning): - if is_use_mitigation: - quantum_instance = QuantumInstance( - backend, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - shots=1024, - measurement_error_mitigation_cls=CompleteMeasFitter_IG, - ) - with self.assertWarnsRegex(DeprecationWarning, r".*ignis.*"): - counts_array[idx] = quantum_instance.execute(qc_meas).get_counts() - else: - quantum_instance = QuantumInstance( - backend, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - shots=1024, - ) - counts_array[idx] = quantum_instance.execute(qc_meas).get_counts() - self.assertEqual( - counts_array[0], counts_array[1], msg="Counts different with/without fitter." - ) - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required for this test") - def test_circuit_modified(self): - """tests that circuits don't get modified on QI execute with error mitigation - as per issue #7449 - """ - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 1679 - np.random.seed(algorithm_globals.random_seed) - - circuit = QuantumCircuit(1) - circuit.x(0) - circuit.measure_all() - - with self.assertWarns(DeprecationWarning): - qi = QuantumInstance( - Aer.get_backend("aer_simulator"), - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - shots=1024, - measurement_error_mitigation_cls=CompleteMeasFitter, - ) - # The error happens on transpiled circuits since "execute" was changing the input array - # Non transpiled circuits didn't have a problem because a new transpiled array was created - # internally. - circuits_ref = qi.transpile(circuit) # always returns a new array - circuits_input = circuits_ref.copy() - - with self.assertWarns(DeprecationWarning): - _ = qi.execute(circuits_input, had_transpiled=True) - self.assertEqual(circuits_ref, circuits_input, msg="Transpiled circuit array modified.") - - @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required for this test") - def test_tensor_subset_fitter(self): - """Test the subset fitter method of the tensor fitter.""" - - # Construct a noise model where readout has errors of different strengths. - noise_model = noise.NoiseModel() - # big error - read_err0 = noise.errors.readout_error.ReadoutError([[0.90, 0.10], [0.25, 0.75]]) - # ideal - read_err1 = noise.errors.readout_error.ReadoutError([[1.00, 0.00], [0.00, 1.00]]) - # small error - read_err2 = noise.errors.readout_error.ReadoutError([[0.98, 0.02], [0.03, 0.97]]) - noise_model.add_readout_error(read_err0, (0,)) - noise_model.add_readout_error(read_err1, (1,)) - noise_model.add_readout_error(read_err2, (2,)) - - mit_pattern = [[idx] for idx in range(3)] - backend = Aer.get_backend("aer_simulator") - backend.set_options(seed_simulator=123) - - with self.assertWarns(DeprecationWarning): - mit_circuits = build_measurement_error_mitigation_circuits( - [0, 1, 2], - TensoredMeasFitter, - backend, - backend_config={}, - compile_config={}, - mit_pattern=mit_pattern, - ) - result = execute(mit_circuits[0], backend, noise_model=noise_model).result() - fitter = TensoredMeasFitter(result, mit_pattern=mit_pattern) - - cal_matrices = fitter.cal_matrices - - # Check that permutations and permuted subsets match. - for subset in [[1, 0], [1, 2], [0, 2], [2, 0, 1]]: - with self.subTest(subset=subset): - with self.assertWarns(DeprecationWarning): - new_fitter = fitter.subset_fitter(subset) - - for idx, qubit in enumerate(subset): - self.assertTrue(np.allclose(new_fitter.cal_matrices[idx], cal_matrices[qubit])) - - self.assertRaisesRegex( - QiskitError, - "Qubit 3 is not in the mit pattern", - fitter.subset_fitter, - [0, 2, 3], - ) - - # Test that we properly correct a circuit with permuted measurements. - circuit = QuantumCircuit(3, 3) - circuit.x(range(3)) - circuit.measure(1, 0) - circuit.measure(2, 1) - circuit.measure(0, 2) - - result = execute( - circuit, backend, noise_model=noise_model, shots=1000, seed_simulator=0 - ).result() - with self.subTest(subset=subset): - with self.assertWarns(DeprecationWarning): - new_result = fitter.subset_fitter([1, 2, 0]).filter.apply(result) - - # The noisy result should have a poor 111 state, the mit. result should be good. - self.assertTrue(result.get_counts()["111"] < 800) - self.assertTrue(new_result.get_counts()["111"] > 990) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_numpy_eigen_solver.py b/test/python/algorithms/test_numpy_eigen_solver.py deleted file mode 100644 index 36d2b66148d0..000000000000 --- a/test/python/algorithms/test_numpy_eigen_solver.py +++ /dev/null @@ -1,210 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test NumPy Eigen solver""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -from ddt import data, ddt - -from qiskit.algorithms import NumPyEigensolver -from qiskit.opflow import PauliSumOp, X, Y, Z - - -@ddt -class TestNumPyEigensolver(QiskitAlgorithmsTestCase): - """Test NumPy Eigen solver""" - - def setUp(self): - super().setUp() - with self.assertWarns(DeprecationWarning): - self.qubit_op = PauliSumOp.from_list( - [ - ("II", -1.052373245772859), - ("ZI", 0.39793742484318045), - ("IZ", -0.39793742484318045), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423156), - ] - ) - - def test_ce(self): - """Test basics""" - with self.assertWarns(DeprecationWarning): - algo = NumPyEigensolver() - result = algo.compute_eigenvalues(operator=self.qubit_op, aux_operators=[]) - - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - - def test_ce_k4(self): - """Test for k=4 eigenvalues""" - with self.assertWarns(DeprecationWarning): - algo = NumPyEigensolver(k=4) - result = algo.compute_eigenvalues(operator=self.qubit_op, aux_operators=[]) - - self.assertEqual(len(result.eigenvalues), 4) - self.assertEqual(len(result.eigenstates), 4) - self.assertEqual(result.eigenvalues.dtype, np.float64) - np.testing.assert_array_almost_equal( - result.eigenvalues, [-1.85727503, -1.24458455, -0.88272215, -0.22491125] - ) - - def test_ce_k4_filtered(self): - """Test for k=4 eigenvalues with filter""" - - # define filter criterion - # pylint: disable=unused-argument - def criterion(x, v, a_v): - return v >= -1 - - with self.assertWarns(DeprecationWarning): - algo = NumPyEigensolver(k=4, filter_criterion=criterion) - result = algo.compute_eigenvalues(operator=self.qubit_op, aux_operators=[]) - - self.assertEqual(len(result.eigenvalues), 2) - self.assertEqual(len(result.eigenstates), 2) - self.assertEqual(result.eigenvalues.dtype, np.float64) - np.testing.assert_array_almost_equal(result.eigenvalues, [-0.88272215, -0.22491125]) - - def test_ce_k4_filtered_empty(self): - """Test for k=4 eigenvalues with filter always returning False""" - - # define filter criterion - # pylint: disable=unused-argument - def criterion(x, v, a_v): - return False - - with self.assertWarns(DeprecationWarning): - algo = NumPyEigensolver(k=4, filter_criterion=criterion) - result = algo.compute_eigenvalues(operator=self.qubit_op, aux_operators=[]) - self.assertEqual(len(result.eigenvalues), 0) - self.assertEqual(len(result.eigenstates), 0) - - @data(X, Y, Z) - def test_ce_k1_1q(self, op): - """Test for 1 qubit operator""" - - with self.assertWarns(DeprecationWarning): - algo = NumPyEigensolver(k=1) - result = algo.compute_eigenvalues(operator=op) - np.testing.assert_array_almost_equal(result.eigenvalues, [-1]) - - @data(X, Y, Z) - def test_ce_k2_1q(self, op): - """Test for 1 qubit operator""" - - with self.assertWarns(DeprecationWarning): - algo = NumPyEigensolver(k=2) - result = algo.compute_eigenvalues(operator=op) - np.testing.assert_array_almost_equal(result.eigenvalues, [-1, 1]) - - def test_aux_operators_list(self): - """Test list-based aux_operators.""" - - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - - with self.assertWarns(DeprecationWarning): - algo = NumPyEigensolver() - result = algo.compute_eigenvalues(operator=self.qubit_op, aux_operators=aux_ops) - - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operator_eigenvalues), 1) - self.assertEqual(len(result.aux_operator_eigenvalues[0]), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][0], 0, places=6) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][1], 0.0) - - # Go again with additional None and zero operators - extra_ops = [*aux_ops, None, 0] - - with self.assertWarns(DeprecationWarning): - result = algo.compute_eigenvalues(operator=self.qubit_op, aux_operators=extra_ops) - - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operator_eigenvalues), 1) - self.assertEqual(len(result.aux_operator_eigenvalues[0]), 4) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][0], 0, places=6) - self.assertIsNone(result.aux_operator_eigenvalues[0][2], None) - self.assertEqual(result.aux_operator_eigenvalues[0][3][0], 0.0) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][1], 0.0) - self.assertEqual(result.aux_operator_eigenvalues[0][3][1], 0.0) - - def test_aux_operators_dict(self): - """Test dict-based aux_operators.""" - - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = {"aux_op1": aux_op1, "aux_op2": aux_op2} - - with self.assertWarns(DeprecationWarning): - algo = NumPyEigensolver() - result = algo.compute_eigenvalues(operator=self.qubit_op, aux_operators=aux_ops) - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operator_eigenvalues), 1) - self.assertEqual(len(result.aux_operator_eigenvalues[0]), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op2"][0], 0, places=6) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op1"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op2"][1], 0.0) - - # Go again with additional None and zero operators - extra_ops = {**aux_ops, "None_operator": None, "zero_operator": 0} - - with self.assertWarns(DeprecationWarning): - result = algo.compute_eigenvalues(operator=self.qubit_op, aux_operators=extra_ops) - - self.assertEqual(len(result.eigenvalues), 1) - self.assertEqual(len(result.eigenstates), 1) - self.assertEqual(result.eigenvalues.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operator_eigenvalues), 1) - self.assertEqual(len(result.aux_operator_eigenvalues[0]), 3) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op2"][0], 0, places=6) - self.assertEqual(result.aux_operator_eigenvalues[0]["zero_operator"][0], 0.0) - self.assertTrue("None_operator" not in result.aux_operator_eigenvalues[0].keys()) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op1"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op2"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["zero_operator"][1], 0.0) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_numpy_minimum_eigen_solver.py b/test/python/algorithms/test_numpy_minimum_eigen_solver.py deleted file mode 100644 index 8f50d5738338..000000000000 --- a/test/python/algorithms/test_numpy_minimum_eigen_solver.py +++ /dev/null @@ -1,277 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test NumPy Minimum Eigensolver""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -from ddt import ddt, data - -from qiskit.algorithms import NumPyMinimumEigensolver -from qiskit.opflow import PauliSumOp, X, Y, Z - - -@ddt -class TestNumPyMinimumEigensolver(QiskitAlgorithmsTestCase): - """Test NumPy Minimum Eigensolver""" - - def setUp(self): - super().setUp() - with self.assertWarns(DeprecationWarning): - self.qubit_op = PauliSumOp.from_list( - [ - ("II", -1.052373245772859), - ("ZI", 0.39793742484318045), - ("IZ", -0.39793742484318045), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423156), - ] - ) - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - - self.aux_ops_list = [aux_op1, aux_op2] - self.aux_ops_dict = {"aux_op1": aux_op1, "aux_op2": aux_op2} - - def test_cme(self): - """Basic test""" - - with self.assertWarns(DeprecationWarning): - algo = NumPyMinimumEigensolver() - result = algo.compute_minimum_eigenvalue( - operator=self.qubit_op, aux_operators=self.aux_ops_list - ) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[0], [2, 0]) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[1], [0, 0]) - - def test_cme_reuse(self): - """Test reuse""" - # Start with no operator or aux_operators, give via compute method - with self.assertWarns(DeprecationWarning): - algo = NumPyMinimumEigensolver() - result = algo.compute_minimum_eigenvalue(operator=self.qubit_op) - - self.assertEqual(result.eigenvalue.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalue, -1.85727503) - self.assertIsNone(result.aux_operator_eigenvalues) - - # Add aux_operators and go again - with self.assertWarns(DeprecationWarning): - result = algo.compute_minimum_eigenvalue( - operator=self.qubit_op, aux_operators=self.aux_ops_list - ) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[0], [2, 0]) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[1], [0, 0]) - - # "Remove" aux_operators and go again - with self.assertWarns(DeprecationWarning): - result = algo.compute_minimum_eigenvalue(operator=self.qubit_op, aux_operators=[]) - - self.assertEqual(result.eigenvalue.dtype, np.float64) - self.assertAlmostEqual(result.eigenvalue, -1.85727503) - self.assertIsNone(result.aux_operator_eigenvalues) - - # Set aux_operators and go again - with self.assertWarns(DeprecationWarning): - result = algo.compute_minimum_eigenvalue( - operator=self.qubit_op, aux_operators=self.aux_ops_list - ) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[0], [2, 0]) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[1], [0, 0]) - - # Finally just set one of aux_operators and main operator, remove aux_operators - - with self.assertWarns(DeprecationWarning): - result = algo.compute_minimum_eigenvalue( - operator=self.aux_ops_list[0], aux_operators=[] - ) - - self.assertAlmostEqual(result.eigenvalue, 2 + 0j) - self.assertIsNone(result.aux_operator_eigenvalues) - - def test_cme_filter(self): - """Basic test""" - - # define filter criterion - # pylint: disable=unused-argument - def criterion(x, v, a_v): - return v >= -0.5 - - with self.assertWarns(DeprecationWarning): - algo = NumPyMinimumEigensolver(filter_criterion=criterion) - result = algo.compute_minimum_eigenvalue( - operator=self.qubit_op, aux_operators=self.aux_ops_list - ) - - self.assertAlmostEqual(result.eigenvalue, -0.22491125 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[0], [2, 0]) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues[1], [0, 0]) - - def test_cme_filter_empty(self): - """Test with filter always returning False""" - - # define filter criterion - # pylint: disable=unused-argument - def criterion(x, v, a_v): - return False - - with self.assertWarns(DeprecationWarning): - algo = NumPyMinimumEigensolver(filter_criterion=criterion) - result = algo.compute_minimum_eigenvalue( - operator=self.qubit_op, aux_operators=self.aux_ops_list - ) - - self.assertEqual(result.eigenvalue, None) - self.assertEqual(result.eigenstate, None) - self.assertEqual(result.aux_operator_eigenvalues, None) - - @data(X, Y, Z) - def test_cme_1q(self, op): - """Test for 1 qubit operator""" - - with self.assertWarns(DeprecationWarning): - algo = NumPyMinimumEigensolver() - result = algo.compute_minimum_eigenvalue(operator=op) - - self.assertAlmostEqual(result.eigenvalue, -1) - - def test_cme_aux_ops_dict(self): - """Test dictionary compatibility of aux_operators""" - # Start with an empty dictionary - with self.assertWarns(DeprecationWarning): - algo = NumPyMinimumEigensolver() - result = algo.compute_minimum_eigenvalue(operator=self.qubit_op, aux_operators={}) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertIsNone(result.aux_operator_eigenvalues) - - # Add aux_operators dictionary and go again - with self.assertWarns(DeprecationWarning): - result = algo.compute_minimum_eigenvalue( - operator=self.qubit_op, aux_operators=self.aux_ops_dict - ) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues["aux_op1"], [2, 0]) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues["aux_op2"], [0, 0]) - - # Add None and zero operators and go again - extra_ops = {"None_op": None, "zero_op": 0, **self.aux_ops_dict} - with self.assertWarns(DeprecationWarning): - result = algo.compute_minimum_eigenvalue( - operator=self.qubit_op, aux_operators=extra_ops - ) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 3) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues["aux_op1"], [2, 0]) - np.testing.assert_array_almost_equal(result.aux_operator_eigenvalues["aux_op2"], [0, 0]) - self.assertEqual(result.aux_operator_eigenvalues["zero_op"], (0.0, 0)) - - def test_aux_operators_list(self): - """Test list-based aux_operators.""" - - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - - with self.assertWarns(DeprecationWarning): - algo = NumPyMinimumEigensolver() - result = algo.compute_minimum_eigenvalue(operator=self.qubit_op, aux_operators=aux_ops) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][0], 0, places=6) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][1], 0.0) - - # Go again with additional None and zero operators - extra_ops = [*aux_ops, None, 0] - - with self.assertWarns(DeprecationWarning): - result = algo.compute_minimum_eigenvalue( - operator=self.qubit_op, aux_operators=extra_ops - ) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 4) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][0], 0, places=6) - self.assertIsNone(result.aux_operator_eigenvalues[2], None) - self.assertEqual(result.aux_operator_eigenvalues[3][0], 0.0) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][1], 0.0) - self.assertEqual(result.aux_operator_eigenvalues[3][1], 0.0) - - def test_aux_operators_dict(self): - """Test dict-based aux_operators.""" - - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = {"aux_op1": aux_op1, "aux_op2": aux_op2} - - with self.assertWarns(DeprecationWarning): - algo = NumPyMinimumEigensolver() - result = algo.compute_minimum_eigenvalue(operator=self.qubit_op, aux_operators=aux_ops) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op2"][0], 0, places=6) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op1"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op2"][1], 0.0) - - # Go again with additional None and zero operators - extra_ops = {**aux_ops, "None_operator": None, "zero_operator": 0} - - with self.assertWarns(DeprecationWarning): - result = algo.compute_minimum_eigenvalue( - operator=self.qubit_op, aux_operators=extra_ops - ) - - self.assertAlmostEqual(result.eigenvalue, -1.85727503 + 0j) - self.assertEqual(len(result.aux_operator_eigenvalues), 3) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op2"][0], 0, places=6) - self.assertEqual(result.aux_operator_eigenvalues["zero_operator"][0], 0.0) - self.assertTrue("None_operator" not in result.aux_operator_eigenvalues.keys()) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op1"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op2"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues["zero_operator"][1], 0.0) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_observables_evaluator.py b/test/python/algorithms/test_observables_evaluator.py deleted file mode 100644 index d4482c9467b5..000000000000 --- a/test/python/algorithms/test_observables_evaluator.py +++ /dev/null @@ -1,189 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Tests evaluator of auxiliary operators for algorithms.""" - -from __future__ import annotations -import unittest -import warnings -from typing import Tuple - -from test.python.algorithms import QiskitAlgorithmsTestCase -import numpy as np -from ddt import ddt, data - -from qiskit.algorithms.list_or_dict import ListOrDict -from qiskit.quantum_info.operators.base_operator import BaseOperator -from qiskit.algorithms import estimate_observables -from qiskit.primitives import Estimator -from qiskit.quantum_info import Statevector, SparsePauliOp -from qiskit import QuantumCircuit -from qiskit.circuit.library import EfficientSU2 -from qiskit.opflow import PauliSumOp -from qiskit.utils import algorithm_globals - - -@ddt -class TestObservablesEvaluator(QiskitAlgorithmsTestCase): - """Tests evaluator of auxiliary operators for algorithms.""" - - def setUp(self): - super().setUp() - self.seed = 50 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - - self.threshold = 1e-8 - - def get_exact_expectation( - self, ansatz: QuantumCircuit, observables: ListOrDict[BaseOperator | PauliSumOp] - ): - """ - Calculates the exact expectation to be used as an expected result for unit tests. - """ - if isinstance(observables, dict): - observables_list = list(observables.values()) - else: - observables_list = observables - # the exact value is a list of (mean, (variance, shots)) where we expect 0 variance and - # 0 shots - exact = [ - (Statevector(ansatz).expectation_value(observable), {}) - for observable in observables_list - ] - - if isinstance(observables, dict): - return dict(zip(observables.keys(), exact)) - - return exact - - def _run_test( - self, - expected_result: ListOrDict[Tuple[complex, complex]], - quantum_state: QuantumCircuit, - decimal: int, - observables: ListOrDict[BaseOperator | PauliSumOp], - estimator: Estimator, - ): - result = estimate_observables(estimator, quantum_state, observables, None, self.threshold) - - if isinstance(observables, dict): - np.testing.assert_equal(list(result.keys()), list(expected_result.keys())) - means = [element[0] for element in result.values()] - expected_means = [element[0] for element in expected_result.values()] - np.testing.assert_array_almost_equal(means, expected_means, decimal=decimal) - - vars_and_shots = [element[1] for element in result.values()] - expected_vars_and_shots = [element[1] for element in expected_result.values()] - np.testing.assert_array_equal(vars_and_shots, expected_vars_and_shots) - else: - means = [element[0] for element in result] - expected_means = [element[0] for element in expected_result] - np.testing.assert_array_almost_equal(means, expected_means, decimal=decimal) - - vars_and_shots = [element[1] for element in result] - expected_vars_and_shots = [element[1] for element in expected_result] - np.testing.assert_array_equal(vars_and_shots, expected_vars_and_shots) - - @data( - [ - PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]), - PauliSumOp.from_list([("II", 2.0)]), - ], - [ - PauliSumOp.from_list([("ZZ", 2.0)]), - ], - { - "op1": PauliSumOp.from_list([("II", 2.0)]), - "op2": PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]), - }, - { - "op1": PauliSumOp.from_list([("ZZ", 2.0)]), - }, - [], - {}, - ) - def test_estimate_observables(self, observables: ListOrDict[BaseOperator | PauliSumOp]): - """Tests evaluator of auxiliary operators for algorithms.""" - - ansatz = EfficientSU2(2) - parameters = np.array( - [1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0], - dtype=float, - ) - - bound_ansatz = ansatz.assign_parameters(parameters) - states = bound_ansatz - expected_result = self.get_exact_expectation(bound_ansatz, observables) - estimator = Estimator() - decimal = 6 - self._run_test( - expected_result, - states, - decimal, - observables, - estimator, - ) - - def test_estimate_observables_zero_op(self): - """Tests if a zero operator is handled correctly.""" - ansatz = EfficientSU2(2) - parameters = np.array( - [1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0], - dtype=float, - ) - - bound_ansatz = ansatz.assign_parameters(parameters) - state = bound_ansatz - estimator = Estimator() - observables = [SparsePauliOp(["XX", "YY"]), 0] - result = estimate_observables(estimator, state, observables, None, self.threshold) - expected_result = [(0.015607318055509564, {}), (0.0, {})] - means = [element[0] for element in result] - expected_means = [element[0] for element in expected_result] - np.testing.assert_array_almost_equal(means, expected_means, decimal=0.01) - - vars_and_shots = [element[1] for element in result] - expected_vars_and_shots = [element[1] for element in expected_result] - np.testing.assert_array_equal(vars_and_shots, expected_vars_and_shots) - - def test_estimate_observables_shots(self): - """Tests that variances and shots are returned properly.""" - ansatz = EfficientSU2(2) - parameters = np.array( - [1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0, 1.2, 4.2, 1.4, 2.0], - dtype=float, - ) - - bound_ansatz = ansatz.assign_parameters(parameters) - state = bound_ansatz - estimator = Estimator(options={"shots": 2048}) - with self.assertWarns(DeprecationWarning): - observables = [PauliSumOp.from_list([("ZZ", 2.0)])] - result = estimate_observables(estimator, state, observables, None, self.threshold) - exact_result = self.get_exact_expectation(bound_ansatz, observables) - expected_result = [(exact_result[0][0], {"variance": 1.0898, "shots": 2048})] - - means = [element[0] for element in result] - expected_means = [element[0] for element in expected_result] - np.testing.assert_array_almost_equal(means, expected_means, decimal=0.01) - - vars_and_shots = [element[1] for element in result] - expected_vars_and_shots = [element[1] for element in expected_result] - for computed, expected in zip(vars_and_shots, expected_vars_and_shots): - self.assertAlmostEqual(computed.pop("variance"), expected.pop("variance"), 2) - self.assertEqual(computed.pop("shots"), expected.pop("shots")) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_phase_estimator.py b/test/python/algorithms/test_phase_estimator.py deleted file mode 100644 index 9040bc1d6912..000000000000 --- a/test/python/algorithms/test_phase_estimator.py +++ /dev/null @@ -1,665 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test phase estimation""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import ddt, data, unpack -import numpy as np -from qiskit.circuit.library import ZGate, XGate, HGate, IGate -from qiskit.quantum_info import Pauli, SparsePauliOp, Statevector, Operator -from qiskit.synthesis import MatrixExponential, SuzukiTrotter -from qiskit.primitives import Sampler -from qiskit.algorithms import PhaseEstimationScale -from qiskit.algorithms.phase_estimators import ( - PhaseEstimation, - HamiltonianPhaseEstimation, - IterativePhaseEstimation, -) -import qiskit -from qiskit import QuantumCircuit -from qiskit.opflow import ( - H, - X, - Y, - Z, - I, - StateFn, - PauliTrotterEvolution, - MatrixEvolution, - PauliSumOp, -) -from qiskit.test import slow_test - - -@ddt -class TestHamiltonianPhaseEstimation(QiskitAlgorithmsTestCase): - """Tests for obtaining eigenvalues from phase estimation""" - - def hamiltonian_pe( - self, - hamiltonian, - state_preparation=None, - num_evaluation_qubits=6, - backend=None, - evolution=None, - bound=None, - ): - """Run HamiltonianPhaseEstimation and return result with all phases.""" - if backend is None: - backend = qiskit.BasicAer.get_backend("statevector_simulator") - - with self.assertWarns(DeprecationWarning): - quantum_instance = qiskit.utils.QuantumInstance(backend=backend, shots=10000) - - with self.assertWarns(DeprecationWarning): - phase_est = HamiltonianPhaseEstimation( - num_evaluation_qubits=num_evaluation_qubits, quantum_instance=quantum_instance - ) - result = phase_est.estimate( - hamiltonian=hamiltonian, - state_preparation=state_preparation, - evolution=evolution, - bound=bound, - ) - return result - - @data(MatrixEvolution(), PauliTrotterEvolution("suzuki", 4)) - def test_pauli_sum_1(self, evolution): - """Two eigenvalues from Pauli sum with X, Z""" - with self.assertWarns(DeprecationWarning): - hamiltonian = 0.5 * X + Z - state_preparation = StateFn(H.to_circuit()) - result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=evolution) - - phase_dict = result.filter_phases(0.162, as_float=True) - phases = list(phase_dict.keys()) - phases.sort() - - self.assertAlmostEqual(phases[0], -1.125, delta=0.001) - self.assertAlmostEqual(phases[1], 1.125, delta=0.001) - - @data(MatrixEvolution(), PauliTrotterEvolution("suzuki", 3)) - def test_pauli_sum_2(self, evolution): - """Two eigenvalues from Pauli sum with X, Y, Z""" - with self.assertWarns(DeprecationWarning): - hamiltonian = 0.5 * X + Y + Z - state_preparation = None - result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=evolution) - - phase_dict = result.filter_phases(0.1, as_float=True) - phases = list(phase_dict.keys()) - phases.sort() - - self.assertAlmostEqual(phases[0], -1.484, delta=0.001) - self.assertAlmostEqual(phases[1], 1.484, delta=0.001) - - def test_single_pauli_op(self): - """Two eigenvalues from Pauli sum with X, Y, Z""" - hamiltonian = Z - state_preparation = None - with self.assertWarns(DeprecationWarning): - result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=None) - - eigv = result.most_likely_eigenvalue - with self.subTest("First eigenvalue"): - self.assertAlmostEqual(eigv, 1.0, delta=0.001) - - with self.assertWarns(DeprecationWarning): - state_preparation = StateFn(X.to_circuit()) - result = self.hamiltonian_pe(hamiltonian, state_preparation, bound=1.05) - - eigv = result.most_likely_eigenvalue - with self.subTest("Second eigenvalue"): - self.assertAlmostEqual(eigv, -0.98, delta=0.01) - - @slow_test - def test_H2_hamiltonian(self): - """Test H2 hamiltonian""" - with self.assertWarns(DeprecationWarning): - hamiltonian = ( - (-1.0523732457728587 * (I ^ I)) - + (0.3979374248431802 * (I ^ Z)) - + (-0.3979374248431802 * (Z ^ I)) - + (-0.011280104256235324 * (Z ^ Z)) - + (0.18093119978423147 * (X ^ X)) - ) - state_preparation = StateFn((I ^ H).to_circuit()) - evo = PauliTrotterEvolution(trotter_mode="suzuki", reps=4) - with self.assertWarns(DeprecationWarning): - result = self.hamiltonian_pe(hamiltonian, state_preparation, evolution=evo) - with self.subTest("Most likely eigenvalues"): - self.assertAlmostEqual(result.most_likely_eigenvalue, -1.855, delta=0.001) - with self.subTest("Most likely phase"): - self.assertAlmostEqual(result.phase, 0.5937, delta=0.001) - with self.subTest("All eigenvalues"): - phase_dict = result.filter_phases(0.1) - phases = list(phase_dict.keys()) - self.assertAlmostEqual(phases[0], -0.8979, delta=0.001) - self.assertAlmostEqual(phases[1], -1.8551, delta=0.001) - self.assertAlmostEqual(phases[2], -1.2376, delta=0.001) - - def test_matrix_evolution(self): - """1Q Hamiltonian with MatrixEvolution""" - with self.assertWarns(DeprecationWarning): - hamiltonian = (0.5 * X) + (0.6 * Y) + (0.7 * I) - state_preparation = None - result = self.hamiltonian_pe( - hamiltonian, state_preparation, evolution=MatrixEvolution() - ) - phase_dict = result.filter_phases(0.2, as_float=True) - phases = list(phase_dict.keys()) - self.assertAlmostEqual(phases[0], 1.490, delta=0.001) - self.assertAlmostEqual(phases[1], -0.090, delta=0.001) - - def _setup_from_bound(self, evolution, op_class): - with self.assertWarns(DeprecationWarning): - hamiltonian = 0.5 * X + Y + Z - state_preparation = None - bound = 1.2 * sum(abs(hamiltonian.coeff * coeff) for coeff in hamiltonian.coeffs) - if op_class == "MatrixOp": - hamiltonian = hamiltonian.to_matrix_op() - backend = qiskit.BasicAer.get_backend("statevector_simulator") - - with self.assertWarns(DeprecationWarning): - qi = qiskit.utils.QuantumInstance(backend=backend, shots=10000) - with self.assertWarns(DeprecationWarning): - phase_est = HamiltonianPhaseEstimation(num_evaluation_qubits=6, quantum_instance=qi) - - result = phase_est.estimate( - hamiltonian=hamiltonian, - bound=bound, - evolution=evolution, - state_preparation=state_preparation, - ) - return result - - def test_from_bound(self): - """HamiltonianPhaseEstimation with bound""" - with self.assertWarns(DeprecationWarning): - for op_class in ("SummedOp", "MatrixOp"): - result = self._setup_from_bound(MatrixEvolution(), op_class) - cutoff = 0.01 - phases = result.filter_phases(cutoff) - with self.subTest(f"test phases has the correct length: {op_class}"): - self.assertEqual(len(phases), 2) - with self.subTest(f"test scaled phases are correct: {op_class}"): - self.assertEqual(list(phases.keys()), [1.5, -1.5]) - phases = result.filter_phases(cutoff, scaled=False) - with self.subTest(f"test unscaled phases are correct: {op_class}"): - self.assertEqual(list(phases.keys()), [0.25, 0.75]) - - def test_trotter_from_bound(self): - """HamiltonianPhaseEstimation with bound and Trotterization""" - with self.assertWarns(DeprecationWarning): - result = self._setup_from_bound( - PauliTrotterEvolution(trotter_mode="suzuki", reps=3), op_class="SummedOp" - ) - phase_dict = result.filter_phases(0.1) - phases = list(phase_dict.keys()) - with self.subTest("test phases has the correct length"): - self.assertEqual(len(phases), 2) - with self.subTest("test phases has correct values"): - self.assertAlmostEqual(phases[0], 1.5, delta=0.001) - self.assertAlmostEqual(phases[1], -1.5, delta=0.001) - - # sampler tests - def hamiltonian_pe_sampler( - self, - hamiltonian, - state_preparation=None, - num_evaluation_qubits=6, - evolution=None, - bound=None, - uses_opflow=True, - ): - """Run HamiltonianPhaseEstimation and return result with all phases.""" - sampler = Sampler() - phase_est = HamiltonianPhaseEstimation( - num_evaluation_qubits=num_evaluation_qubits, sampler=sampler - ) - if uses_opflow: - with self.assertWarns(DeprecationWarning): - result = phase_est.estimate( - hamiltonian=hamiltonian, - state_preparation=state_preparation, - evolution=evolution, - bound=bound, - ) - else: - result = phase_est.estimate( - hamiltonian=hamiltonian, - state_preparation=state_preparation, - evolution=evolution, - bound=bound, - ) - return result - - @data(MatrixExponential(), SuzukiTrotter(reps=4)) - def test_pauli_sum_1_sampler(self, evolution): - """Two eigenvalues from Pauli sum with X, Z""" - with self.assertWarns(DeprecationWarning): - hamiltonian = PauliSumOp(SparsePauliOp.from_list([("X", 0.5), ("Z", 1)])) - state_preparation = QuantumCircuit(1).compose(HGate()) - - result = self.hamiltonian_pe_sampler(hamiltonian, state_preparation, evolution=evolution) - phase_dict = result.filter_phases(0.162, as_float=True) - phases = list(phase_dict.keys()) - phases.sort() - - self.assertAlmostEqual(phases[0], -1.125, delta=0.001) - self.assertAlmostEqual(phases[1], 1.125, delta=0.001) - - @data(MatrixExponential(), SuzukiTrotter(reps=3)) - def test_pauli_sum_2_sampler(self, evolution): - """Two eigenvalues from Pauli sum with X, Y, Z""" - with self.assertWarns(DeprecationWarning): - hamiltonian = PauliSumOp(SparsePauliOp.from_list([("X", 0.5), ("Z", 1), ("Y", 1)])) - state_preparation = None - - result = self.hamiltonian_pe_sampler(hamiltonian, state_preparation, evolution=evolution) - phase_dict = result.filter_phases(0.1, as_float=True) - phases = list(phase_dict.keys()) - phases.sort() - - self.assertAlmostEqual(phases[0], -1.484, delta=0.001) - self.assertAlmostEqual(phases[1], 1.484, delta=0.001) - - def test_single_pauli_op_sampler(self): - """Two eigenvalues from Pauli sum with X, Y, Z""" - hamiltonian = SparsePauliOp(Pauli("Z")) - state_preparation = None - - result = self.hamiltonian_pe_sampler( - hamiltonian, state_preparation, evolution=None, uses_opflow=False - ) - eigv = result.most_likely_eigenvalue - with self.subTest("First eigenvalue"): - self.assertAlmostEqual(eigv, 1.0, delta=0.001) - - state_preparation = QuantumCircuit(1).compose(XGate()) - - result = self.hamiltonian_pe_sampler( - hamiltonian, state_preparation, bound=1.05, uses_opflow=False - ) - eigv = result.most_likely_eigenvalue - with self.subTest("Second eigenvalue"): - self.assertAlmostEqual(eigv, -0.98, delta=0.01) - - @data( - Statevector(QuantumCircuit(2).compose(IGate()).compose(HGate())), - QuantumCircuit(2).compose(IGate()).compose(HGate()), - ) - def test_H2_hamiltonian_sampler(self, state_preparation): - """Test H2 hamiltonian""" - - with self.assertWarns(DeprecationWarning): - hamiltonian = PauliSumOp( - SparsePauliOp.from_list( - [ - ("II", -1.0523732457728587), - ("IZ", 0.3979374248431802), - ("ZI", -0.3979374248431802), - ("ZZ", -0.011280104256235324), - ("XX", 0.18093119978423147), - ] - ) - ) - - evo = SuzukiTrotter(reps=4) - result = self.hamiltonian_pe_sampler(hamiltonian, state_preparation, evolution=evo) - with self.subTest("Most likely eigenvalues"): - self.assertAlmostEqual(result.most_likely_eigenvalue, -1.855, delta=0.001) - with self.subTest("Most likely phase"): - self.assertAlmostEqual(result.phase, 0.5937, delta=0.001) - with self.subTest("All eigenvalues"): - phase_dict = result.filter_phases(0.1) - phases = sorted(phase_dict.keys()) - self.assertAlmostEqual(phases[0], -1.8551, delta=0.001) - self.assertAlmostEqual(phases[1], -1.2376, delta=0.001) - self.assertAlmostEqual(phases[2], -0.8979, delta=0.001) - - def test_matrix_evolution_sampler(self): - """1Q Hamiltonian with MatrixEvolution""" - with self.assertWarns(DeprecationWarning): - hamiltonian = PauliSumOp(SparsePauliOp.from_list([("X", 0.5), ("Y", 0.6), ("I", 0.7)])) - state_preparation = None - result = self.hamiltonian_pe_sampler( - hamiltonian, state_preparation, evolution=MatrixExponential() - ) - phase_dict = result.filter_phases(0.2, as_float=True) - phases = sorted(phase_dict.keys()) - self.assertAlmostEqual(phases[0], -0.090, delta=0.001) - self.assertAlmostEqual(phases[1], 1.490, delta=0.001) - - -@ddt -class TestPhaseEstimation(QiskitAlgorithmsTestCase): - """Evolution tests.""" - - def one_phase( - self, - unitary_circuit, - state_preparation=None, - backend_type=None, - phase_estimator=None, - num_iterations=6, - ): - """Run phase estimation with operator, eigenvalue pair `unitary_circuit`, - `state_preparation`. Return the estimated phase as a value in :math:`[0,1)`. - """ - if backend_type is None: - backend_type = "qasm_simulator" - backend = qiskit.BasicAer.get_backend(backend_type) - if phase_estimator is None: - phase_estimator = IterativePhaseEstimation - - with self.assertWarns(DeprecationWarning): - qi = qiskit.utils.QuantumInstance(backend=backend, shots=10000) - - with self.assertWarns(DeprecationWarning): - - if phase_estimator == IterativePhaseEstimation: - p_est = IterativePhaseEstimation(num_iterations=num_iterations, quantum_instance=qi) - elif phase_estimator == PhaseEstimation: - p_est = PhaseEstimation(num_evaluation_qubits=6, quantum_instance=qi) - else: - raise ValueError("Unrecognized phase_estimator") - - result = p_est.estimate(unitary=unitary_circuit, state_preparation=state_preparation) - phase = result.phase - return phase - - @data( - (X.to_circuit(), 0.5, "statevector_simulator", IterativePhaseEstimation), - (X.to_circuit(), 0.5, "qasm_simulator", IterativePhaseEstimation), - (None, 0.0, "qasm_simulator", IterativePhaseEstimation), - (X.to_circuit(), 0.5, "qasm_simulator", PhaseEstimation), - (None, 0.0, "qasm_simulator", PhaseEstimation), - (X.to_circuit(), 0.5, "statevector_simulator", PhaseEstimation), - ) - @unpack - def test_qpe_Z(self, state_preparation, expected_phase, backend_type, phase_estimator): - """eigenproblem Z, |0> and |1>""" - unitary_circuit = Z.to_circuit() - with self.assertWarns(DeprecationWarning): - phase = self.one_phase( - unitary_circuit, - state_preparation, - backend_type=backend_type, - phase_estimator=phase_estimator, - ) - self.assertEqual(phase, expected_phase) - - @data( - (H.to_circuit(), 0.0, IterativePhaseEstimation), - ((H @ X).to_circuit(), 0.5, IterativePhaseEstimation), - (H.to_circuit(), 0.0, PhaseEstimation), - ((H @ X).to_circuit(), 0.5, PhaseEstimation), - ) - @unpack - def test_qpe_X_plus_minus(self, state_preparation, expected_phase, phase_estimator): - """eigenproblem X, (|+>, |->)""" - unitary_circuit = X.to_circuit() - with self.assertWarns(DeprecationWarning): - phase = self.one_phase( - unitary_circuit, state_preparation, phase_estimator=phase_estimator - ) - self.assertEqual(phase, expected_phase) - - @data( - (X.to_circuit(), 0.125, IterativePhaseEstimation), - (I.to_circuit(), 0.875, IterativePhaseEstimation), - (X.to_circuit(), 0.125, PhaseEstimation), - (I.to_circuit(), 0.875, PhaseEstimation), - ) - @unpack - def test_qpe_RZ(self, state_preparation, expected_phase, phase_estimator): - """eigenproblem RZ, (|0>, |1>)""" - alpha = np.pi / 2 - unitary_circuit = QuantumCircuit(1) - unitary_circuit.rz(alpha, 0) - with self.assertWarns(DeprecationWarning): - phase = self.one_phase( - unitary_circuit, state_preparation, phase_estimator=phase_estimator - ) - self.assertEqual(phase, expected_phase) - - def test_check_num_iterations(self): - """test check for num_iterations greater than zero""" - unitary_circuit = X.to_circuit() - state_preparation = None - with self.assertRaises(ValueError): - self.one_phase(unitary_circuit, state_preparation, num_iterations=-1) - - def phase_estimation( - self, - unitary_circuit, - state_preparation=None, - num_evaluation_qubits=6, - backend=None, - construct_circuit=False, - ): - """Run phase estimation with operator, eigenvalue pair `unitary_circuit`, - `state_preparation`. Return all results - """ - if backend is None: - backend = qiskit.BasicAer.get_backend("statevector_simulator") - - with self.assertWarns(DeprecationWarning): - qi = qiskit.utils.QuantumInstance(backend=backend, shots=10000) - with self.assertWarns(DeprecationWarning): - phase_est = PhaseEstimation( - num_evaluation_qubits=num_evaluation_qubits, quantum_instance=qi - ) - if construct_circuit: - pe_circuit = phase_est.construct_circuit(unitary_circuit, state_preparation) - result = phase_est.estimate_from_pe_circuit(pe_circuit, unitary_circuit.num_qubits) - else: - result = phase_est.estimate( - unitary=unitary_circuit, state_preparation=state_preparation - ) - - return result - - @data(True, False) - def test_qpe_Zplus(self, construct_circuit): - """superposition eigenproblem Z, |+>""" - unitary_circuit = Z.to_circuit() - state_preparation = H.to_circuit() # prepare |+> - - with self.assertWarns(DeprecationWarning): - result = self.phase_estimation( - unitary_circuit, - state_preparation, - backend=qiskit.BasicAer.get_backend("statevector_simulator"), - construct_circuit=construct_circuit, - ) - - phases = result.filter_phases(1e-15, as_float=True) - with self.subTest("test phases has correct values"): - self.assertEqual(list(phases.keys()), [0.0, 0.5]) - - with self.subTest("test phases has correct probabilities"): - np.testing.assert_allclose(list(phases.values()), [0.5, 0.5]) - - with self.subTest("test bitstring representation"): - phases = result.filter_phases(1e-15, as_float=False) - self.assertEqual(list(phases.keys()), ["000000", "100000"]) - - # sampler tests - def one_phase_sampler( - self, - unitary_circuit, - state_preparation=None, - phase_estimator=None, - num_iterations=6, - shots=None, - ): - """Run phase estimation with operator, eigenvalue pair `unitary_circuit`, - `state_preparation`. Return the estimated phase as a value in :math:`[0,1)`. - """ - if shots is not None: - options = {"shots": shots} - else: - options = {} - sampler = Sampler(options=options) - if phase_estimator is None: - phase_estimator = IterativePhaseEstimation - if phase_estimator == IterativePhaseEstimation: - p_est = IterativePhaseEstimation(num_iterations=num_iterations, sampler=sampler) - elif phase_estimator == PhaseEstimation: - p_est = PhaseEstimation(num_evaluation_qubits=6, sampler=sampler) - else: - raise ValueError("Unrecognized phase_estimator") - result = p_est.estimate(unitary=unitary_circuit, state_preparation=state_preparation) - phase = result.phase - return phase - - @data( - (QuantumCircuit(1).compose(XGate()), 0.5, None, IterativePhaseEstimation), - (QuantumCircuit(1).compose(XGate()), 0.5, 1000, IterativePhaseEstimation), - (None, 0.0, 1000, IterativePhaseEstimation), - (QuantumCircuit(1).compose(XGate()), 0.5, 1000, PhaseEstimation), - (None, 0.0, 1000, PhaseEstimation), - (QuantumCircuit(1).compose(XGate()), 0.5, None, PhaseEstimation), - ) - @unpack - def test_qpe_Z_sampler(self, state_preparation, expected_phase, shots, phase_estimator): - """eigenproblem Z, |0> and |1>""" - unitary_circuit = QuantumCircuit(1).compose(ZGate()) - phase = self.one_phase_sampler( - unitary_circuit, - state_preparation=state_preparation, - phase_estimator=phase_estimator, - shots=shots, - ) - self.assertEqual(phase, expected_phase) - - @data( - (QuantumCircuit(1).compose(HGate()), 0.0, IterativePhaseEstimation), - (QuantumCircuit(1).compose(HGate()).compose(ZGate()), 0.5, IterativePhaseEstimation), - (QuantumCircuit(1).compose(HGate()), 0.0, PhaseEstimation), - (QuantumCircuit(1).compose(HGate()).compose(ZGate()), 0.5, PhaseEstimation), - ) - @unpack - def test_qpe_X_plus_minus_sampler(self, state_preparation, expected_phase, phase_estimator): - """eigenproblem X, (|+>, |->)""" - unitary_circuit = QuantumCircuit(1).compose(XGate()) - phase = self.one_phase_sampler( - unitary_circuit, - state_preparation, - phase_estimator, - ) - self.assertEqual(phase, expected_phase) - - @data( - (QuantumCircuit(1).compose(XGate()), 0.125, IterativePhaseEstimation), - (QuantumCircuit(1).compose(IGate()), 0.875, IterativePhaseEstimation), - (QuantumCircuit(1).compose(XGate()), 0.125, PhaseEstimation), - (QuantumCircuit(1).compose(IGate()), 0.875, PhaseEstimation), - ) - @unpack - def test_qpe_RZ_sampler(self, state_preparation, expected_phase, phase_estimator): - """eigenproblem RZ, (|0>, |1>)""" - alpha = np.pi / 2 - unitary_circuit = QuantumCircuit(1) - unitary_circuit.rz(alpha, 0) - phase = self.one_phase_sampler( - unitary_circuit, - state_preparation, - phase_estimator, - ) - self.assertEqual(phase, expected_phase) - - @data( - ((X ^ X).to_circuit(), 0.25, IterativePhaseEstimation), - ((I ^ X).to_circuit(), 0.125, IterativePhaseEstimation), - ((X ^ X).to_circuit(), 0.25, PhaseEstimation), - ((I ^ X).to_circuit(), 0.125, PhaseEstimation), - ) - @unpack - def test_qpe_two_qubit_unitary(self, state_preparation, expected_phase, phase_estimator): - """two qubit unitary T ^ T""" - unitary_circuit = QuantumCircuit(2) - unitary_circuit.t(0) - unitary_circuit.t(1) - phase = self.one_phase_sampler( - unitary_circuit, - state_preparation, - phase_estimator, - ) - self.assertEqual(phase, expected_phase) - - def test_check_num_iterations_sampler(self): - """test check for num_iterations greater than zero""" - unitary_circuit = QuantumCircuit(1).compose(XGate()) - state_preparation = None - with self.assertRaises(ValueError): - self.one_phase_sampler(unitary_circuit, state_preparation, num_iterations=-1) - - def test_phase_estimation_scale_from_operator(self): - """test that PhaseEstimationScale from_pauli_sum works with Operator""" - circ = QuantumCircuit(2) - op = Operator(circ) - scale = PhaseEstimationScale.from_pauli_sum(op) - self.assertEqual(scale._bound, 4.0) - - def phase_estimation_sampler( - self, - unitary_circuit, - sampler: Sampler, - state_preparation=None, - num_evaluation_qubits=6, - construct_circuit=False, - ): - """Run phase estimation with operator, eigenvalue pair `unitary_circuit`, - `state_preparation`. Return all results - """ - phase_est = PhaseEstimation(num_evaluation_qubits=num_evaluation_qubits, sampler=sampler) - if construct_circuit: - pe_circuit = phase_est.construct_circuit(unitary_circuit, state_preparation) - result = phase_est.estimate_from_pe_circuit(pe_circuit, unitary_circuit.num_qubits) - else: - result = phase_est.estimate( - unitary=unitary_circuit, state_preparation=state_preparation - ) - return result - - @data(True, False) - def test_qpe_Zplus_sampler(self, construct_circuit): - """superposition eigenproblem Z, |+>""" - unitary_circuit = QuantumCircuit(1).compose(ZGate()) - state_preparation = QuantumCircuit(1).compose(HGate()) # prepare |+> - sampler = Sampler() - result = self.phase_estimation_sampler( - unitary_circuit, - sampler, - state_preparation, - construct_circuit=construct_circuit, - ) - - phases = result.filter_phases(1e-15, as_float=True) - with self.subTest("test phases has correct values"): - self.assertEqual(list(phases.keys()), [0.0, 0.5]) - - with self.subTest("test phases has correct probabilities"): - np.testing.assert_allclose(list(phases.values()), [0.5, 0.5]) - - with self.subTest("test bitstring representation"): - phases = result.filter_phases(1e-15, as_float=False) - self.assertEqual(list(phases.keys()), ["000000", "100000"]) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_qaoa.py b/test/python/algorithms/test_qaoa.py deleted file mode 100644 index 5edb525022a3..000000000000 --- a/test/python/algorithms/test_qaoa.py +++ /dev/null @@ -1,410 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test QAOA""" - -import unittest -import warnings -from test.python.algorithms import QiskitAlgorithmsTestCase - -from functools import partial -import math -import numpy as np -from scipy.optimize import minimize as scipy_minimize -from ddt import ddt, idata, unpack -import rustworkx as rx - -from qiskit.algorithms import QAOA -from qiskit.algorithms.optimizers import COBYLA, NELDER_MEAD - -from qiskit.opflow import I, X, Z, PauliSumOp - -from qiskit import BasicAer, QuantumCircuit, QuantumRegister - -from qiskit.circuit import Parameter -from qiskit.quantum_info import Pauli -from qiskit.utils import QuantumInstance, algorithm_globals - -W1 = np.array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]]) -P1 = 1 -M1 = (I ^ I ^ I ^ X) + (I ^ I ^ X ^ I) + (I ^ X ^ I ^ I) + (X ^ I ^ I ^ I) -S1 = {"0101", "1010"} - - -W2 = np.array( - [ - [0.0, 8.0, -9.0, 0.0], - [8.0, 0.0, 7.0, 9.0], - [-9.0, 7.0, 0.0, -8.0], - [0.0, 9.0, -8.0, 0.0], - ] -) -P2 = 1 -M2 = None -S2 = {"1011", "0100"} - -CUSTOM_SUPERPOSITION = [1 / math.sqrt(15)] * 15 + [0] - - -@ddt -class TestQAOA(QiskitAlgorithmsTestCase): - """Test QAOA with MaxCut.""" - - def setUp(self): - super().setUp() - self.seed = 10598 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - - with self.assertWarns(DeprecationWarning): - self.qasm_simulator = QuantumInstance( - BasicAer.get_backend("qasm_simulator"), - shots=4096, - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - self.statevector_simulator = QuantumInstance( - BasicAer.get_backend("statevector_simulator"), - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - - @idata( - [ - [W1, P1, M1, S1, False], - [W2, P2, M2, S2, False], - [W1, P1, M1, S1, True], - [W2, P2, M2, S2, True], - ] - ) - @unpack - def test_qaoa(self, w, prob, m, solutions, convert_to_matrix_op): - """QAOA test""" - self.log.debug("Testing %s-step QAOA with MaxCut on graph\n%s", prob, w) - - qubit_op, _ = self._get_operator(w) - - if convert_to_matrix_op: - with self.assertWarns(DeprecationWarning): - qubit_op = qubit_op.to_matrix_op() - - with self.assertWarns(DeprecationWarning): - qaoa = QAOA(COBYLA(), prob, mixer=m, quantum_instance=self.statevector_simulator) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - self.assertIn(graph_solution, solutions) - - @idata( - [ - [W1, P1, S1, False], - [W2, P2, S2, False], - [W1, P1, S1, True], - [W2, P2, S2, True], - ] - ) - @unpack - def test_qaoa_qc_mixer(self, w, prob, solutions, convert_to_matrix_op): - """QAOA test with a mixer as a parameterized circuit""" - self.log.debug( - "Testing %s-step QAOA with MaxCut on graph with a mixer as a parameterized circuit\n%s", - prob, - w, - ) - - optimizer = COBYLA() - qubit_op, _ = self._get_operator(w) - if convert_to_matrix_op: - with self.assertWarns(DeprecationWarning): - qubit_op = qubit_op.to_matrix_op() - - num_qubits = qubit_op.num_qubits - mixer = QuantumCircuit(num_qubits) - theta = Parameter("θ") - mixer.rx(theta, range(num_qubits)) - - with self.assertWarns(DeprecationWarning): - qaoa = QAOA(optimizer, prob, mixer=mixer, quantum_instance=self.statevector_simulator) - - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - self.assertIn(graph_solution, solutions) - - def test_qaoa_qc_mixer_many_parameters(self): - """QAOA test with a mixer as a parameterized circuit with the num of parameters > 1.""" - optimizer = COBYLA() - qubit_op, _ = self._get_operator(W1) - - num_qubits = qubit_op.num_qubits - mixer = QuantumCircuit(num_qubits) - for i in range(num_qubits): - theta = Parameter("θ" + str(i)) - mixer.rx(theta, range(num_qubits)) - - with self.assertWarns(DeprecationWarning): - qaoa = QAOA(optimizer, reps=2, mixer=mixer, quantum_instance=self.statevector_simulator) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - x = self._sample_most_likely(result.eigenstate) - self.log.debug(x) - graph_solution = self._get_graph_solution(x) - self.assertIn(graph_solution, S1) - - def test_qaoa_qc_mixer_no_parameters(self): - """QAOA test with a mixer as a parameterized circuit with zero parameters.""" - qubit_op, _ = self._get_operator(W1) - - num_qubits = qubit_op.num_qubits - mixer = QuantumCircuit(num_qubits) - # just arbitrary circuit - mixer.rx(np.pi / 2, range(num_qubits)) - - with self.assertWarns(DeprecationWarning): - qaoa = QAOA(COBYLA(), reps=1, mixer=mixer, quantum_instance=self.statevector_simulator) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - # we just assert that we get a result, it is not meaningful. - self.assertIsNotNone(result.eigenstate) - - def test_change_operator_size(self): - """QAOA change operator size test""" - qubit_op, _ = self._get_operator( - np.array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 1, 0]]) - ) - with self.assertWarns(DeprecationWarning): - qaoa = QAOA(COBYLA(), 1, quantum_instance=self.statevector_simulator) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - with self.subTest(msg="QAOA 4x4"): - self.assertIn(graph_solution, {"0101", "1010"}) - - qubit_op, _ = self._get_operator( - np.array( - [ - [0, 1, 0, 1, 0, 1], - [1, 0, 1, 0, 1, 0], - [0, 1, 0, 1, 0, 1], - [1, 0, 1, 0, 1, 0], - [0, 1, 0, 1, 0, 1], - [1, 0, 1, 0, 1, 0], - ] - ) - ) - with self.assertWarns(DeprecationWarning): - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - with self.subTest(msg="QAOA 6x6"): - self.assertIn(graph_solution, {"010101", "101010"}) - - @idata([[W2, S2, None], [W2, S2, [0.0, 0.0]], [W2, S2, [1.0, 0.8]]]) - @unpack - def test_qaoa_initial_point(self, w, solutions, init_pt): - """Check first parameter value used is initial point as expected""" - qubit_op, _ = self._get_operator(w) - - first_pt = [] - - def cb_callback(eval_count, parameters, mean, std): - nonlocal first_pt - if eval_count == 1: - first_pt = list(parameters) - - with self.assertWarns(DeprecationWarning): - qaoa = QAOA( - COBYLA(), - initial_point=init_pt, - callback=cb_callback, - quantum_instance=self.statevector_simulator, - ) - - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - x = self._sample_most_likely(result.eigenstate) - graph_solution = self._get_graph_solution(x) - - with self.subTest("Initial Point"): - # If None the preferred random initial point of QAOA variational form - if init_pt is None: - self.assertLess(result.eigenvalue, -0.97) - else: - self.assertListEqual(init_pt, first_pt) - - with self.subTest("Solution"): - self.assertIn(graph_solution, solutions) - - @idata([[W2, None], [W2, [1.0] + 15 * [0.0]], [W2, CUSTOM_SUPERPOSITION]]) - @unpack - def test_qaoa_initial_state(self, w, init_state): - """QAOA initial state test""" - optimizer = COBYLA() - qubit_op, _ = self._get_operator(w) - - init_pt = np.asarray([0.0, 0.0]) # Avoid generating random initial point - - if init_state is None: - initial_state = None - else: - initial_state = QuantumCircuit(QuantumRegister(4, "q")) - initial_state.initialize(init_state, initial_state.qubits) - - zero_init_state = QuantumCircuit(QuantumRegister(qubit_op.num_qubits, "q")) - - with self.assertWarns(DeprecationWarning): - qaoa_zero_init_state = QAOA( - optimizer=optimizer, - initial_state=zero_init_state, - initial_point=init_pt, - quantum_instance=self.statevector_simulator, - ) - qaoa = QAOA( - optimizer=optimizer, - initial_state=initial_state, - initial_point=init_pt, - quantum_instance=self.statevector_simulator, - ) - zero_circuits = qaoa_zero_init_state.construct_circuit(init_pt, qubit_op) - custom_circuits = qaoa.construct_circuit(init_pt, qubit_op) - - self.assertEqual(len(zero_circuits), len(custom_circuits)) - - for zero_circ, custom_circ in zip(zero_circuits, custom_circuits): - - z_length = len(zero_circ.data) - c_length = len(custom_circ.data) - - self.assertGreaterEqual(c_length, z_length) - self.assertTrue(zero_circ.data == custom_circ.data[-z_length:]) - - custom_init_qc = QuantumCircuit(custom_circ.num_qubits) - custom_init_qc.data = custom_circ.data[0 : c_length - z_length] - - if initial_state is None: - original_init_qc = QuantumCircuit(qubit_op.num_qubits) - original_init_qc.h(range(qubit_op.num_qubits)) - else: - original_init_qc = initial_state - - with self.assertWarns(DeprecationWarning): - job_init_state = self.statevector_simulator.execute(original_init_qc) - job_qaoa_init_state = self.statevector_simulator.execute(custom_init_qc) - - statevector_original = job_init_state.get_statevector(original_init_qc) - statevector_custom = job_qaoa_init_state.get_statevector(custom_init_qc) - - self.assertListEqual(statevector_original.tolist(), statevector_custom.tolist()) - - def test_qaoa_random_initial_point(self): - """QAOA random initial point""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - w = rx.adjacency_matrix( - rx.undirected_gnp_random_graph(5, 0.5, seed=algorithm_globals.random_seed) - ) - qubit_op, _ = self._get_operator(w) - - with self.assertWarns(DeprecationWarning): - qaoa = QAOA( - optimizer=NELDER_MEAD(disp=True), reps=1, quantum_instance=self.qasm_simulator - ) - result = qaoa.compute_minimum_eigenvalue(operator=qubit_op) - - self.assertLess(result.eigenvalue, -0.97) - - def test_qaoa_construct_circuit_update(self): - """Test updating operators with QAOA construct_circuit""" - with self.assertWarns(DeprecationWarning): - qaoa = QAOA() - ref = qaoa.construct_circuit([0, 0], I ^ Z)[0] - circ2 = qaoa.construct_circuit([0, 0], I ^ Z)[0] - self.assertEqual(circ2, ref) - circ3 = qaoa.construct_circuit([0, 0], Z ^ I)[0] - self.assertNotEqual(circ3, ref) - circ4 = qaoa.construct_circuit([0, 0], I ^ Z)[0] - self.assertEqual(circ4, ref) - - def test_optimizer_scipy_callable(self): - """Test passing a SciPy optimizer directly as callable.""" - with self.assertWarns(DeprecationWarning): - qaoa = QAOA( - optimizer=partial(scipy_minimize, method="Nelder-Mead", options={"maxiter": 2}), - quantum_instance=self.statevector_simulator, - ) - result = qaoa.compute_minimum_eigenvalue(Z) - self.assertEqual(result.cost_function_evals, 4) - - def _get_operator(self, weight_matrix): - """Generate Hamiltonian for the max-cut problem of a graph. - - Args: - weight_matrix (numpy.ndarray) : adjacency matrix. - - Returns: - PauliSumOp: operator for the Hamiltonian - float: a constant shift for the obj function. - - """ - num_nodes = weight_matrix.shape[0] - pauli_list = [] - shift = 0 - for i in range(num_nodes): - for j in range(i): - if weight_matrix[i, j] != 0: - x_p = np.zeros(num_nodes, dtype=bool) - z_p = np.zeros(num_nodes, dtype=bool) - z_p[i] = True - z_p[j] = True - pauli_list.append([0.5 * weight_matrix[i, j], Pauli((z_p, x_p))]) - shift -= 0.5 * weight_matrix[i, j] - opflow_list = [(pauli[1].to_label(), pauli[0]) for pauli in pauli_list] - - with self.assertWarns(DeprecationWarning): - return PauliSumOp.from_list(opflow_list), shift - - def _get_graph_solution(self, x: np.ndarray) -> str: - """Get graph solution from binary string. - - Args: - x : binary string as numpy array. - - Returns: - a graph solution as string. - """ - - return "".join([str(int(i)) for i in 1 - x]) - - def _sample_most_likely(self, state_vector): - """Compute the most likely binary string from state vector. - Args: - state_vector (numpy.ndarray or dict): state vector or counts. - - Returns: - numpy.ndarray: binary string as numpy.ndarray of ints. - """ - n = int(np.log2(state_vector.shape[0])) - k = np.argmax(np.abs(state_vector)) - x = np.zeros(n) - for i in range(n): - x[i] = k % 2 - k >>= 1 - return x - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_skip_qobj_validation.py b/test/python/algorithms/test_skip_qobj_validation.py deleted file mode 100644 index c5af2b7dc279..000000000000 --- a/test/python/algorithms/test_skip_qobj_validation.py +++ /dev/null @@ -1,148 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test Skip Qobj Validation""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister -from qiskit import BasicAer -from qiskit.utils import QuantumInstance -from qiskit.exceptions import QiskitError - - -def _compare_dict(dict1, dict2): - equal = True - for key1, value1 in dict1.items(): - if key1 not in dict2: - equal = False - break - if value1 != dict2[key1]: - equal = False - break - return equal - - -class TestSkipQobjValidation(QiskitAlgorithmsTestCase): - """Test Skip Qobj Validation""" - - def setUp(self): - super().setUp() - self.random_seed = 10598 - - # ┌───┐ ░ ┌─┐ ░ - # q0_0: ┤ H ├──■───░─┤M├─░──── - # └───┘┌─┴─┐ ░ └╥┘ ░ ┌─┐ - # q0_1: ─────┤ X ├─░──╫──░─┤M├ - # └───┘ ░ ║ ░ └╥┘ - # c0: 2/══════════════╩═════╩═ - # 0 1 - qr = QuantumRegister(2) - cr = ClassicalRegister(2) - qc = QuantumCircuit(qr, cr) - qc.h(qr[0]) - qc.cx(qr[0], qr[1]) - # Ensure qubit 0 is measured before qubit 1 - qc.barrier(qr) - qc.measure(qr[0], cr[0]) - qc.barrier(qr) - qc.measure(qr[1], cr[1]) - - self.qc = qc - self.backend = BasicAer.get_backend("qasm_simulator") - - def test_wo_backend_options(self): - """without backend options test""" - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - self.backend, - seed_transpiler=self.random_seed, - seed_simulator=self.random_seed, - shots=1024, - ) - # run without backend_options and without noise - res_wo_bo = quantum_instance.execute(self.qc).get_counts(self.qc) - self.assertGreaterEqual(quantum_instance.time_taken, 0.0) - quantum_instance.reset_execution_results() - quantum_instance.skip_qobj_validation = True - res_wo_bo_skip_validation = quantum_instance.execute(self.qc).get_counts(self.qc) - - self.assertGreaterEqual(quantum_instance.time_taken, 0.0) - quantum_instance.reset_execution_results() - self.assertTrue(_compare_dict(res_wo_bo, res_wo_bo_skip_validation)) - - def test_w_backend_options(self): - """with backend options test""" - # run with backend_options - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - self.backend, - seed_transpiler=self.random_seed, - seed_simulator=self.random_seed, - shots=1024, - backend_options={"initial_statevector": [0.5, 0.5, 0.5, 0.5]}, - ) - res_w_bo = quantum_instance.execute(self.qc).get_counts(self.qc) - self.assertGreaterEqual(quantum_instance.time_taken, 0.0) - quantum_instance.reset_execution_results() - quantum_instance.skip_qobj_validation = True - res_w_bo_skip_validation = quantum_instance.execute(self.qc).get_counts(self.qc) - - self.assertGreaterEqual(quantum_instance.time_taken, 0.0) - quantum_instance.reset_execution_results() - self.assertTrue(_compare_dict(res_w_bo, res_w_bo_skip_validation)) - - def test_w_noise(self): - """with noise test""" - # build noise model - # Asymmetric readout error on qubit-0 only - try: - from qiskit.providers.aer.noise import NoiseModel - from qiskit import Aer - - self.backend = Aer.get_backend("qasm_simulator") - - except ImportError as ex: - self.skipTest(f"Aer doesn't appear to be installed. Error: '{str(ex)}'") - return - - probs_given0 = [0.9, 0.1] - probs_given1 = [0.3, 0.7] - noise_model = NoiseModel() - noise_model.add_readout_error([probs_given0, probs_given1], [0]) - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - self.backend, - seed_transpiler=self.random_seed, - seed_simulator=self.random_seed, - shots=1024, - noise_model=noise_model, - ) - res_w_noise = quantum_instance.execute(self.qc).get_counts(self.qc) - quantum_instance.skip_qobj_validation = True - res_w_noise_skip_validation = quantum_instance.execute(self.qc).get_counts(self.qc) - - self.assertTrue(_compare_dict(res_w_noise, res_w_noise_skip_validation)) - - with self.assertWarns(DeprecationWarning): - # BasicAer should fail: - with self.assertRaises(QiskitError): - _ = QuantumInstance(BasicAer.get_backend("qasm_simulator"), noise_model=noise_model) - - with self.assertRaises(QiskitError): - quantum_instance = QuantumInstance(BasicAer.get_backend("qasm_simulator")) - quantum_instance.set_config(noise_model=noise_model) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_validation.py b/test/python/algorithms/test_validation.py deleted file mode 100644 index 90a1b9eed143..000000000000 --- a/test/python/algorithms/test_validation.py +++ /dev/null @@ -1,94 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test Validation""" - -import unittest - -from test.python.algorithms import QiskitAlgorithmsTestCase -from qiskit.utils.validation import ( - validate_in_set, - validate_min, - validate_min_exclusive, - validate_max, - validate_max_exclusive, - validate_range, - validate_range_exclusive, - validate_range_exclusive_min, - validate_range_exclusive_max, -) - - -class TestValidation(QiskitAlgorithmsTestCase): - """Validation tests.""" - - def test_validate_in_set(self): - """validate in set test""" - test_value = "value1" - with self.assertWarns(DeprecationWarning): - validate_in_set("test_value", test_value, {"value1", "value2"}) - with self.assertRaises(ValueError): - validate_in_set("test_value", test_value, {"value3", "value4"}) - - def test_validate_min(self): - """validate min test""" - test_value = 2.5 - with self.assertWarns(DeprecationWarning): - validate_min("test_value", test_value, -1) - validate_min("test_value", test_value, 2.5) - with self.assertRaises(ValueError): - validate_min("test_value", test_value, 4) - validate_min_exclusive("test_value", test_value, -1) - with self.assertRaises(ValueError): - validate_min_exclusive("test_value", test_value, 2.5) - with self.assertRaises(ValueError): - validate_min_exclusive("test_value", test_value, 4) - - def test_validate_max(self): - """validate max test""" - test_value = 2.5 - with self.assertWarns(DeprecationWarning): - with self.assertRaises(ValueError): - validate_max("test_value", test_value, -1) - validate_max("test_value", test_value, 2.5) - validate_max("test_value", test_value, 4) - with self.assertRaises(ValueError): - validate_max_exclusive("test_value", test_value, -1) - with self.assertRaises(ValueError): - validate_max_exclusive("test_value", test_value, 2.5) - validate_max_exclusive("test_value", test_value, 4) - - def test_validate_range(self): - """validate range test""" - test_value = 2.5 - with self.assertWarns(DeprecationWarning): - with self.assertRaises(ValueError): - validate_range("test_value", test_value, 0, 2) - with self.assertRaises(ValueError): - validate_range("test_value", test_value, 3, 4) - validate_range("test_value", test_value, 2.5, 3) - validate_range_exclusive("test_value", test_value, 0, 3) - with self.assertRaises(ValueError): - validate_range_exclusive("test_value", test_value, 0, 2.5) - validate_range_exclusive("test_value", test_value, 2.5, 3) - validate_range_exclusive_min("test_value", test_value, 0, 3) - with self.assertRaises(ValueError): - validate_range_exclusive_min("test_value", test_value, 2.5, 3) - validate_range_exclusive_min("test_value", test_value, 0, 2.5) - validate_range_exclusive_max("test_value", test_value, 2.5, 3) - with self.assertRaises(ValueError): - validate_range_exclusive_max("test_value", test_value, 0, 2.5) - validate_range_exclusive_max("test_value", test_value, 2.5, 3) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_vqd.py b/test/python/algorithms/test_vqd.py deleted file mode 100644 index e153ba50ddb7..000000000000 --- a/test/python/algorithms/test_vqd.py +++ /dev/null @@ -1,663 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test VQD""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase - -import numpy as np -from ddt import data, ddt, unpack - -from qiskit import BasicAer, QuantumCircuit -from qiskit.algorithms import VQD, AlgorithmError -from qiskit.algorithms.optimizers import ( - COBYLA, - L_BFGS_B, - SLSQP, -) -from qiskit.circuit.library import EfficientSU2, RealAmplitudes, TwoLocal -from qiskit.exceptions import MissingOptionalLibraryError -from qiskit.opflow import ( - AerPauliExpectation, - I, - MatrixExpectation, - MatrixOp, - PauliExpectation, - PauliSumOp, - PrimitiveOp, - X, - Z, -) - -from qiskit.utils import QuantumInstance, algorithm_globals, has_aer -from qiskit.test import slow_test - - -if has_aer(): - from qiskit import Aer - - -@ddt -class TestVQD(QiskitAlgorithmsTestCase): - """Test VQD""" - - def setUp(self): - super().setUp() - self.seed = 50 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - self.h2_energy = -1.85727503 - self.h2_energy_excited = [-1.85727503, -1.24458455] - - self.test_results = [-3, -1] - - self.ryrz_wavefunction = TwoLocal( - rotation_blocks=["ry", "rz"], entanglement_blocks="cz", reps=1 - ) - self.ry_wavefunction = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz") - - with self.assertWarns(DeprecationWarning): - self.h2_op = ( - -1.052373245772859 * (I ^ I) - + 0.39793742484318045 * (I ^ Z) - - 0.39793742484318045 * (Z ^ I) - - 0.01128010425623538 * (Z ^ Z) - + 0.18093119978423156 * (X ^ X) - ) - self.test_op = MatrixOp(np.diagflat([3, 5, -1, 0.8, 0.2, 2, 1, -3])).to_pauli_op() - self.qasm_simulator = QuantumInstance( - BasicAer.get_backend("qasm_simulator"), - shots=2048, - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - self.statevector_simulator = QuantumInstance( - BasicAer.get_backend("statevector_simulator"), - shots=1, - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - - @slow_test - def test_basic_aer_statevector(self): - """Test the VQD on BasicAer's statevector simulator.""" - wavefunction = self.ryrz_wavefunction - - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=2, - ansatz=wavefunction, - optimizer=COBYLA(), - quantum_instance=QuantumInstance( - BasicAer.get_backend("statevector_simulator"), - basis_gates=["u1", "u2", "u3", "cx", "id"], - coupling_map=[[0, 1]], - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ), - ) - - result = vqd.compute_eigenvalues(operator=self.h2_op) - - with self.subTest(msg="test eigenvalue"): - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=1 - ) - - with self.subTest(msg="test dimension of optimal point"): - self.assertEqual(len(result.optimal_point[-1]), 8) - - with self.subTest(msg="assert cost_function_evals is set"): - self.assertIsNotNone(result.cost_function_evals) - - with self.subTest(msg="assert optimizer_time is set"): - self.assertIsNotNone(result.optimizer_time) - - def test_mismatching_num_qubits(self): - """Ensuring circuit and operator mismatch is caught""" - wavefunction = QuantumCircuit(1) - optimizer = SLSQP(maxiter=50) - - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=1, - ansatz=wavefunction, - optimizer=optimizer, - quantum_instance=self.statevector_simulator, - ) - with self.assertRaises(AlgorithmError): - _ = vqd.compute_eigenvalues(operator=self.h2_op) - - @data( - (MatrixExpectation(), 1), - (AerPauliExpectation(), 1), - (PauliExpectation(), 2), - ) - @unpack - def test_construct_circuit(self, expectation, num_circuits): - """Test construct circuits returns QuantumCircuits and the right number of them.""" - try: - wavefunction = EfficientSU2(2, reps=1) - - with self.assertWarns(DeprecationWarning): - vqd = VQD(k=2, ansatz=wavefunction, expectation=expectation) - params = [0] * wavefunction.num_parameters - circuits = vqd.construct_circuit(parameter=params, operator=self.h2_op) - self.assertEqual(len(circuits), num_circuits) - - for circuit in circuits: - self.assertIsInstance(circuit, QuantumCircuit) - - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - return - - def test_missing_varform_params(self): - """Test specifying a variational form with no parameters raises an error.""" - - circuit = QuantumCircuit(self.h2_op.num_qubits) - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=1, ansatz=circuit, quantum_instance=BasicAer.get_backend("statevector_simulator") - ) - with self.assertRaises(RuntimeError): - vqd.compute_eigenvalues(operator=self.h2_op) - - def test_basic_aer_qasm(self): - """Test the VQD on BasicAer's QASM simulator.""" - optimizer = COBYLA(maxiter=1000) - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - vqd = VQD( - ansatz=wavefunction, - optimizer=optimizer, - max_evals_grouped=1, - quantum_instance=self.qasm_simulator, - ) - # TODO benchmark this later. - result = vqd.compute_eigenvalues(operator=self.h2_op) - - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=1 - ) - - @unittest.skipUnless(has_aer(), "qiskit-aer doesn't appear to be installed.") - def test_with_aer_statevector(self): - """Test VQD with Aer's statevector_simulator.""" - backend = Aer.get_backend("aer_simulator_statevector") - wavefunction = self.ry_wavefunction - optimizer = L_BFGS_B() - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=2, - ansatz=wavefunction, - optimizer=optimizer, - max_evals_grouped=1, - quantum_instance=quantum_instance, - ) - result = vqd.compute_eigenvalues(operator=self.h2_op) - - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=2 - ) - - @unittest.skipUnless(has_aer(), "qiskit-aer doesn't appear to be installed.") - def test_with_aer_qasm(self): - """Test VQD with Aer's qasm_simulator.""" - backend = Aer.get_backend("aer_simulator") - optimizer = COBYLA(maxiter=1000) - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=2, - ansatz=wavefunction, - optimizer=optimizer, - expectation=PauliExpectation(), - quantum_instance=quantum_instance, - ) - result = vqd.compute_eigenvalues(operator=self.h2_op) - - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=1 - ) - - @unittest.skipUnless(has_aer(), "qiskit-aer doesn't appear to be installed.") - def test_with_aer_qasm_snapshot_mode(self): - """Test the VQD using Aer's qasm_simulator snapshot mode.""" - - backend = Aer.get_backend("aer_simulator") - optimizer = COBYLA(maxiter=400) - wavefunction = self.ryrz_wavefunction - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend, - shots=100, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=2, - ansatz=wavefunction, - optimizer=optimizer, - expectation=AerPauliExpectation(), - quantum_instance=quantum_instance, - ) - result = vqd.compute_eigenvalues(operator=self.test_op) - - np.testing.assert_array_almost_equal(result.eigenvalues.real, self.test_results, decimal=1) - - def test_callback(self): - """Test the callback on VQD.""" - history = {"eval_count": [], "parameters": [], "mean": [], "std": [], "step": []} - - def store_intermediate_result(eval_count, parameters, mean, std, step): - history["eval_count"].append(eval_count) - history["parameters"].append(parameters) - history["mean"].append(mean) - history["std"].append(std) - history["step"].append(step) - - optimizer = COBYLA(maxiter=3) - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - vqd = VQD( - ansatz=wavefunction, - optimizer=optimizer, - callback=store_intermediate_result, - quantum_instance=self.qasm_simulator, - ) - vqd.compute_eigenvalues(operator=self.h2_op) - - self.assertTrue(all(isinstance(count, int) for count in history["eval_count"])) - self.assertTrue(all(isinstance(mean, float) for mean in history["mean"])) - self.assertTrue(all(isinstance(std, float) for std in history["std"])) - self.assertTrue(all(isinstance(count, int) for count in history["step"])) - for params in history["parameters"]: - self.assertTrue(all(isinstance(param, float) for param in params)) - - ref_eval_count = [1, 2, 3, 1, 2, 3] - ref_mean = [-1.063, -1.457, -1.360, 37.340, 48.543, 28.586] - ref_std = [0.011, 0.010, 0.014, 0.011, 0.010, 0.015] - ref_step = [1, 1, 1, 2, 2, 2] - - np.testing.assert_array_almost_equal(history["eval_count"], ref_eval_count, decimal=0) - np.testing.assert_array_almost_equal(history["mean"], ref_mean, decimal=2) - np.testing.assert_array_almost_equal(history["std"], ref_std, decimal=2) - np.testing.assert_array_almost_equal(history["step"], ref_step, decimal=0) - - def test_reuse(self): - """Test re-using a VQD algorithm instance.""" - - with self.assertWarns(DeprecationWarning): - vqd = VQD(k=1) - - with self.subTest(msg="assert running empty raises AlgorithmError"): - with self.assertWarns(DeprecationWarning), self.assertRaises(AlgorithmError): - _ = vqd.compute_eigenvalues(operator=self.h2_op) - - ansatz = TwoLocal(rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - vqd.ansatz = ansatz - - with self.subTest(msg="assert missing operator raises AlgorithmError"): - with self.assertWarns(DeprecationWarning), self.assertRaises(AlgorithmError): - _ = vqd.compute_eigenvalues(operator=self.h2_op) - - with self.assertWarns(DeprecationWarning): - vqd.expectation = MatrixExpectation() - vqd.quantum_instance = self.statevector_simulator - - with self.subTest(msg="assert VQE works once all info is available"): - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(operator=self.h2_op) - np.testing.assert_array_almost_equal(result.eigenvalues.real, self.h2_energy, decimal=2) - - with self.assertWarns(DeprecationWarning): - operator = PrimitiveOp( - np.array([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, 2, 0], [0, 0, 0, 3]]) - ) - - with self.subTest(msg="assert minimum eigensolver interface works"): - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(operator=operator) - self.assertAlmostEqual(result.eigenvalues.real[0], -1.0, places=5) - - def test_vqd_optimizer(self): - """Test running same VQD twice to re-use optimizer, then switch optimizer""" - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=2, - optimizer=SLSQP(), - quantum_instance=QuantumInstance(BasicAer.get_backend("statevector_simulator")), - ) - - def run_check(): - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(operator=self.h2_op) - - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=3 - ) - - run_check() - - with self.subTest("Optimizer re-use"): - run_check() - - with self.subTest("Optimizer replace"): - vqd.optimizer = L_BFGS_B() - run_check() - - @data(MatrixExpectation(), None) - def test_backend_change(self, user_expectation): - """Test that VQE works when backend changes.""" - - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=1, - ansatz=TwoLocal(rotation_blocks=["ry", "rz"], entanglement_blocks="cz"), - optimizer=SLSQP(maxiter=2), - expectation=user_expectation, - quantum_instance=BasicAer.get_backend("statevector_simulator"), - ) - result0 = vqd.compute_eigenvalues(operator=self.h2_op) - if user_expectation is not None: - with self.subTest("User expectation kept."): - self.assertEqual(vqd.expectation, user_expectation) - - with self.assertWarns(DeprecationWarning): - vqd.quantum_instance = BasicAer.get_backend("qasm_simulator") - # works also if no expectation is set, since it will be determined automatically - - result1 = vqd.compute_eigenvalues(operator=self.h2_op) - - if user_expectation is not None: - with self.subTest("Change backend with user expectation, it is kept."): - self.assertEqual(vqd.expectation, user_expectation) - - with self.subTest("Check results."): - self.assertEqual(len(result0.optimal_point), len(result1.optimal_point)) - - def test_set_ansatz_to_none(self): - """Tests that setting the ansatz to None results in the default behavior""" - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=1, - ansatz=self.ryrz_wavefunction, - optimizer=L_BFGS_B(), - quantum_instance=self.statevector_simulator, - ) - vqd.ansatz = None - self.assertIsInstance(vqd.ansatz, RealAmplitudes) - - def test_set_optimizer_to_none(self): - """Tests that setting the optimizer to None results in the default behavior""" - - with self.assertWarns(DeprecationWarning): - vqd = VQD( - k=1, - ansatz=self.ryrz_wavefunction, - optimizer=L_BFGS_B(), - quantum_instance=self.statevector_simulator, - ) - vqd.optimizer = None - self.assertIsInstance(vqd.optimizer, SLSQP) - - def test_aux_operators_list(self): - """Test list-based aux_operators.""" - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - vqd = VQD(k=2, ansatz=wavefunction, quantum_instance=self.statevector_simulator) - - # Start with an empty list - result = vqd.compute_eigenvalues(self.h2_op, aux_operators=[]) - - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=2 - ) - self.assertIsNone(result.aux_operator_eigenvalues) - - # Go again with two auxiliary operators - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - result = vqd.compute_eigenvalues(self.h2_op, aux_operators=aux_ops) - - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=2 - ) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][0], 2, places=2) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][0], 0, places=2) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][1], 0.0) - - # Go again with additional None and zero operators - extra_ops = [*aux_ops, None, 0] - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(self.h2_op, aux_operators=extra_ops) - - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=2 - ) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][0], 2, places=2) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][0], 0, places=2) - self.assertEqual(result.aux_operator_eigenvalues[0][2][0], 0.0) - self.assertEqual(result.aux_operator_eigenvalues[0][3][0], 0.0) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][1], 0.0) - self.assertEqual(result.aux_operator_eigenvalues[0][3][1], 0.0) - - def test_aux_operators_dict(self): - """Test dictionary compatibility of aux_operators""" - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - vqd = VQD(ansatz=wavefunction, quantum_instance=self.statevector_simulator) - - # Start with an empty dictionary - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(self.h2_op, aux_operators={}) - - np.testing.assert_array_almost_equal( - result.eigenvalues.real, self.h2_energy_excited, decimal=2 - ) - self.assertIsNone(result.aux_operator_eigenvalues) - - # Go again with two auxiliary operators - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = {"aux_op1": aux_op1, "aux_op2": aux_op2} - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(self.h2_op, aux_operators=aux_ops) - - self.assertEqual(len(result.eigenvalues), 2) - self.assertEqual(len(result.eigenstates), 2) - self.assertEqual(result.eigenvalues.dtype, np.complex128) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - self.assertEqual(len(result.aux_operator_eigenvalues[0]), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op2"][0], 0, places=1) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op1"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op2"][1], 0.0) - - # Go again with additional None and zero operators - extra_ops = {**aux_ops, "None_operator": None, "zero_operator": 0} - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(self.h2_op, aux_operators=extra_ops) - self.assertEqual(len(result.eigenvalues), 2) - self.assertEqual(len(result.eigenstates), 2) - self.assertEqual(result.eigenvalues.dtype, np.complex128) - self.assertAlmostEqual(result.eigenvalues[0], -1.85727503) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - self.assertEqual(len(result.aux_operator_eigenvalues[0]), 3) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op2"][0], 0, places=6) - self.assertEqual(result.aux_operator_eigenvalues[0]["zero_operator"][0], 0.0) - self.assertTrue("None_operator" not in result.aux_operator_eigenvalues[0].keys()) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op1"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["aux_op2"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0]["zero_operator"][1], 0.0) - - def test_aux_operator_std_dev_pauli(self): - """Test non-zero standard deviations of aux operators with PauliExpectation.""" - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - vqd = VQD( - ansatz=wavefunction, - expectation=PauliExpectation(), - initial_point=[ - 1.70256666, - -5.34843975, - -0.39542903, - 5.99477786, - -2.74374986, - -4.85284669, - 0.2442925, - -1.51638917, - ], - optimizer=COBYLA(maxiter=0), - quantum_instance=self.qasm_simulator, - ) - - with self.assertWarns(DeprecationWarning): - # Go again with two auxiliary operators - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(self.h2_op, aux_operators=aux_ops) - - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][0], 2.0, places=1) - self.assertAlmostEqual( - result.aux_operator_eigenvalues[0][1][0], 0.0019531249999999445, places=1 - ) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][1], 0.0) - self.assertAlmostEqual( - result.aux_operator_eigenvalues[0][1][1], 0.015183867579396111, places=1 - ) - - # Go again with additional None and zero operators - aux_ops = [*aux_ops, None, 0] - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(self.h2_op, aux_operators=aux_ops) - - self.assertEqual(len(result.aux_operator_eigenvalues[0]), 4) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][0], 2.0, places=1) - self.assertAlmostEqual( - result.aux_operator_eigenvalues[0][1][0], 0.0019531249999999445, places=1 - ) - self.assertEqual(result.aux_operator_eigenvalues[0][2][0], 0.0) - self.assertEqual(result.aux_operator_eigenvalues[0][3][0], 0.0) - # # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][1], 0.0) - self.assertAlmostEqual( - result.aux_operator_eigenvalues[0][1][1], 0.01548658094658011, places=1 - ) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][2][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][3][1], 0.0) - - @unittest.skipUnless(has_aer(), "qiskit-aer doesn't appear to be installed.") - def test_aux_operator_std_dev_aer_pauli(self): - """Test non-zero standard deviations of aux operators with AerPauliExpectation.""" - wavefunction = self.ry_wavefunction - with self.assertWarns(DeprecationWarning): - vqd = VQD( - ansatz=wavefunction, - expectation=AerPauliExpectation(), - optimizer=COBYLA(maxiter=0), - quantum_instance=QuantumInstance( - backend=Aer.get_backend("qasm_simulator"), - shots=1, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ), - ) - - with self.assertWarns(DeprecationWarning): - # Go again with two auxiliary operators - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(self.h2_op, aux_operators=aux_ops) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][0], 2.0, places=1) - self.assertAlmostEqual( - result.aux_operator_eigenvalues[0][1][0], 0.6698863565455391, places=1 - ) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][1], 0.0, places=6) - - # Go again with additional None and zero operators - aux_ops = [*aux_ops, None, 0] - with self.assertWarns(DeprecationWarning): - result = vqd.compute_eigenvalues(self.h2_op, aux_operators=aux_ops) - self.assertEqual(len(result.aux_operator_eigenvalues[-1]), 4) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][0], 2.0, places=6) - self.assertAlmostEqual( - result.aux_operator_eigenvalues[0][1][0], 0.6036400943063891, places=6 - ) - self.assertEqual(result.aux_operator_eigenvalues[0][2][0], 0.0) - self.assertEqual(result.aux_operator_eigenvalues[0][3][0], 0.0) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1][1], 0.0, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][2][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][3][1], 0.0) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/test_vqe.py b/test/python/algorithms/test_vqe.py deleted file mode 100644 index 781728400f2b..000000000000 --- a/test/python/algorithms/test_vqe.py +++ /dev/null @@ -1,856 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test VQE""" - -import logging -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase -from test.python.transpiler._dummy_passes import DummyAP - -from functools import partial -import numpy as np -from scipy.optimize import minimize as scipy_minimize -from ddt import data, ddt, unpack - -from qiskit import BasicAer, QuantumCircuit -from qiskit.algorithms import VQE, AlgorithmError -from qiskit.algorithms.optimizers import ( - CG, - COBYLA, - L_BFGS_B, - P_BFGS, - QNSPSA, - SLSQP, - SPSA, - TNC, - OptimizerResult, -) -from qiskit.circuit.library import EfficientSU2, RealAmplitudes, TwoLocal -from qiskit.exceptions import MissingOptionalLibraryError -from qiskit.opflow import ( - AerPauliExpectation, - Gradient, - I, - MatrixExpectation, - PauliExpectation, - PauliSumOp, - PrimitiveOp, - TwoQubitReduction, - X, - Z, -) -from qiskit.quantum_info import Statevector -from qiskit.transpiler import PassManager, PassManagerConfig -from qiskit.transpiler.preset_passmanagers import level_1_pass_manager -from qiskit.utils import QuantumInstance, algorithm_globals, optionals - -logger = "LocalLogger" - - -class LogPass(DummyAP): - """A dummy analysis pass that logs when executed""" - - def __init__(self, message): - super().__init__() - self.message = message - - def run(self, dag): - logging.getLogger(logger).info(self.message) - - -# pylint: disable=invalid-name, unused-argument -def _mock_optimizer(fun, x0, jac=None, bounds=None) -> OptimizerResult: - """A mock of a callable that can be used as minimizer in the VQE.""" - result = OptimizerResult() - result.x = np.zeros_like(x0) - result.fun = fun(result.x) - result.nit = 0 - return result - - -@ddt -class TestVQE(QiskitAlgorithmsTestCase): - """Test VQE""" - - def setUp(self): - super().setUp() - self.seed = 50 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - self.h2_energy = -1.85727503 - - self.ryrz_wavefunction = TwoLocal(rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - self.ry_wavefunction = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz") - - with self.assertWarns(DeprecationWarning): - self.h2_op = ( - -1.052373245772859 * (I ^ I) - + 0.39793742484318045 * (I ^ Z) - - 0.39793742484318045 * (Z ^ I) - - 0.01128010425623538 * (Z ^ Z) - + 0.18093119978423156 * (X ^ X) - ) - self.qasm_simulator = QuantumInstance( - BasicAer.get_backend("qasm_simulator"), - shots=1024, - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - self.statevector_simulator = QuantumInstance( - BasicAer.get_backend("statevector_simulator"), - shots=1, - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - - def test_basic_aer_statevector(self): - """Test the VQE on BasicAer's statevector simulator.""" - wavefunction = self.ryrz_wavefunction - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - optimizer=L_BFGS_B(), - quantum_instance=QuantumInstance( - BasicAer.get_backend("statevector_simulator"), - basis_gates=["u1", "u2", "u3", "cx", "id"], - coupling_map=[[0, 1]], - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ), - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - with self.subTest(msg="test eigenvalue"): - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy) - - with self.subTest(msg="test dimension of optimal point"): - self.assertEqual(len(result.optimal_point), 16) - - with self.subTest(msg="assert cost_function_evals is set"): - self.assertIsNotNone(result.cost_function_evals) - - with self.subTest(msg="assert optimizer_time is set"): - self.assertIsNotNone(result.optimizer_time) - - def test_circuit_input(self): - """Test running the VQE on a plain QuantumCircuit object.""" - wavefunction = QuantumCircuit(2).compose(EfficientSU2(2)) - optimizer = SLSQP(maxiter=50) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - optimizer=optimizer, - quantum_instance=self.statevector_simulator, - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - - @data( - (MatrixExpectation(), 1), - (AerPauliExpectation(), 1), - (PauliExpectation(), 2), - ) - @unpack - def test_construct_circuit(self, expectation, num_circuits): - """Test construct circuits returns QuantumCircuits and the right number of them.""" - try: - wavefunction = EfficientSU2(2, reps=1) - - with self.assertWarns(DeprecationWarning): - vqe = VQE(ansatz=wavefunction, expectation=expectation) - params = [0] * wavefunction.num_parameters - circuits = vqe.construct_circuit(parameter=params, operator=self.h2_op) - - self.assertEqual(len(circuits), num_circuits) - for circuit in circuits: - self.assertIsInstance(circuit, QuantumCircuit) - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - return - - def test_missing_varform_params(self): - """Test specifying a variational form with no parameters raises an error.""" - circuit = QuantumCircuit(self.h2_op.num_qubits) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=circuit, quantum_instance=BasicAer.get_backend("statevector_simulator") - ) - with self.assertRaises(RuntimeError): - vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - @data( - (SLSQP(maxiter=50), 5, 4), - (SPSA(maxiter=150), 2, 2), # max_evals_grouped=n or =2 if n>2 - ) - @unpack - def test_max_evals_grouped(self, optimizer, places, max_evals_grouped): - """VQE Optimizers test""" - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=self.ryrz_wavefunction, - optimizer=optimizer, - max_evals_grouped=max_evals_grouped, - quantum_instance=self.statevector_simulator, - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=places) - - def test_basic_aer_qasm(self): - """Test the VQE on BasicAer's QASM simulator.""" - optimizer = SPSA(maxiter=300, last_avg=5) - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - optimizer=optimizer, - max_evals_grouped=1, - quantum_instance=self.qasm_simulator, - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.86823, places=2) - - def test_qasm_eigenvector_normalized(self): - """Test VQE with qasm_simulator returns normalized eigenvector.""" - wavefunction = self.ry_wavefunction - with self.assertWarns(DeprecationWarning): - vqe = VQE(ansatz=wavefunction, quantum_instance=self.qasm_simulator) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - amplitudes = list(result.eigenstate.values()) - self.assertAlmostEqual(np.linalg.norm(amplitudes), 1.0, places=4) - - @unittest.skipUnless(optionals.HAS_AER, "Qiskit aer is required to run these tests") - def test_with_aer_statevector(self): - """Test VQE with Aer's statevector_simulator.""" - from qiskit_aer import AerSimulator - - backend = AerSimulator(method="statevector") - wavefunction = self.ry_wavefunction - optimizer = L_BFGS_B() - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - optimizer=optimizer, - max_evals_grouped=1, - quantum_instance=quantum_instance, - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - - @unittest.skipUnless(optionals.HAS_AER, "Qiskit aer is required to run these tests") - def test_with_aer_qasm(self): - """Test VQE with Aer's qasm_simulator.""" - from qiskit_aer import AerSimulator - - backend = AerSimulator() - optimizer = SPSA(maxiter=200, last_avg=5) - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - optimizer=optimizer, - expectation=PauliExpectation(), - quantum_instance=quantum_instance, - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.86305, places=2) - - @unittest.skipUnless(optionals.HAS_AER, "Qiskit aer is required to run these tests") - def test_with_aer_qasm_snapshot_mode(self): - """Test the VQE using Aer's qasm_simulator snapshot mode.""" - from qiskit_aer import AerSimulator - - backend = AerSimulator() - optimizer = L_BFGS_B() - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend, - shots=1, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - optimizer=optimizer, - expectation=AerPauliExpectation(), - quantum_instance=quantum_instance, - ) - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - - @unittest.skipUnless(optionals.HAS_AER, "Qiskit aer is required to run these tests") - @data( - CG(maxiter=1), - L_BFGS_B(maxfun=1), - P_BFGS(maxfun=1, max_processes=0), - SLSQP(maxiter=1), - TNC(maxiter=1), - ) - def test_with_gradient(self, optimizer): - """Test VQE using Gradient().""" - from qiskit_aer import AerSimulator - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=AerSimulator(), - shots=1, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ) - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=self.ry_wavefunction, - optimizer=optimizer, - gradient=Gradient(), - expectation=AerPauliExpectation(), - quantum_instance=quantum_instance, - max_evals_grouped=1000, - ) - vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - def test_with_two_qubit_reduction(self): - """Test the VQE using TwoQubitReduction.""" - - with self.assertWarns(DeprecationWarning): - qubit_op = PauliSumOp.from_list( - [ - ("IIII", -0.8105479805373266), - ("IIIZ", 0.17218393261915552), - ("IIZZ", -0.22575349222402472), - ("IZZI", 0.1721839326191556), - ("ZZII", -0.22575349222402466), - ("IIZI", 0.1209126326177663), - ("IZZZ", 0.16892753870087912), - ("IXZX", -0.045232799946057854), - ("ZXIX", 0.045232799946057854), - ("IXIX", 0.045232799946057854), - ("ZXZX", -0.045232799946057854), - ("ZZIZ", 0.16614543256382414), - ("IZIZ", 0.16614543256382414), - ("ZZZZ", 0.17464343068300453), - ("ZIZI", 0.1209126326177663), - ] - ) - tapered_qubit_op = TwoQubitReduction(num_particles=2).convert(qubit_op) - - for simulator in [self.qasm_simulator, self.statevector_simulator]: - with self.subTest(f"Test for {simulator}."), self.assertWarns(DeprecationWarning): - vqe = VQE( - self.ry_wavefunction, - SPSA(maxiter=300, last_avg=5), - quantum_instance=simulator, - ) - result = vqe.compute_minimum_eigenvalue(tapered_qubit_op) - energy = -1.868 if simulator == self.qasm_simulator else self.h2_energy - self.assertAlmostEqual(result.eigenvalue.real, energy, places=2) - - def test_callback(self): - """Test the callback on VQE.""" - history = {"eval_count": [], "parameters": [], "mean": [], "std": []} - - def store_intermediate_result(eval_count, parameters, mean, std): - history["eval_count"].append(eval_count) - history["parameters"].append(parameters) - history["mean"].append(mean) - history["std"].append(std) - - optimizer = COBYLA(maxiter=3) - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - optimizer=optimizer, - callback=store_intermediate_result, - quantum_instance=self.qasm_simulator, - ) - vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertTrue(all(isinstance(count, int) for count in history["eval_count"])) - self.assertTrue(all(isinstance(mean, float) for mean in history["mean"])) - self.assertTrue(all(isinstance(std, float) for std in history["std"])) - for params in history["parameters"]: - self.assertTrue(all(isinstance(param, float) for param in params)) - - def test_reuse(self): - """Test re-using a VQE algorithm instance.""" - - with self.assertWarns(DeprecationWarning): - vqe = VQE() - with self.subTest(msg="assert running empty raises AlgorithmError"): - with self.assertWarns(DeprecationWarning), self.assertRaises(AlgorithmError): - _ = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - ansatz = TwoLocal(rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - vqe.ansatz = ansatz - with self.subTest(msg="assert missing operator raises AlgorithmError"): - with self.assertWarns(DeprecationWarning), self.assertRaises(AlgorithmError): - _ = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - with self.assertWarns(DeprecationWarning): - vqe.expectation = MatrixExpectation() - vqe.quantum_instance = self.statevector_simulator - - with self.subTest(msg="assert VQE works once all info is available"), self.assertWarns( - DeprecationWarning - ): - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=5) - - with self.assertWarns(DeprecationWarning): - operator = PrimitiveOp( - np.array([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, 2, 0], [0, 0, 0, 3]]) - ) - - with self.subTest(msg="assert minimum eigensolver interface works"), self.assertWarns( - DeprecationWarning - ): - result = vqe.compute_minimum_eigenvalue(operator=operator) - self.assertAlmostEqual(result.eigenvalue.real, -1.0, places=5) - - def test_vqe_optimizer(self): - """Test running same VQE twice to re-use optimizer, then switch optimizer""" - with self.assertWarns(DeprecationWarning): - vqe = VQE( - optimizer=SLSQP(), - quantum_instance=QuantumInstance(BasicAer.get_backend("statevector_simulator")), - ) - - def run_check(): - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - self.assertAlmostEqual(result.eigenvalue.real, -1.85727503, places=5) - - run_check() - - with self.subTest("Optimizer re-use"): - run_check() - - with self.subTest("Optimizer replace"): - vqe.optimizer = L_BFGS_B() - run_check() - - @data(MatrixExpectation(), None) - def test_backend_change(self, user_expectation): - """Test that VQE works when backend changes.""" - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=TwoLocal(rotation_blocks=["ry", "rz"], entanglement_blocks="cz"), - optimizer=SLSQP(maxiter=2), - expectation=user_expectation, - quantum_instance=BasicAer.get_backend("statevector_simulator"), - ) - result0 = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - if user_expectation is not None: - with self.subTest("User expectation kept."): - self.assertEqual(vqe.expectation, user_expectation) - - # works also if no expectation is set, since it will be determined automatically - with self.assertWarns(DeprecationWarning): - vqe.quantum_instance = BasicAer.get_backend("qasm_simulator") - result1 = vqe.compute_minimum_eigenvalue(operator=self.h2_op) - - if user_expectation is not None: - with self.subTest("Change backend with user expectation, it is kept."): - self.assertEqual(vqe.expectation, user_expectation) - - with self.subTest("Check results."): - self.assertEqual(len(result0.optimal_point), len(result1.optimal_point)) - - def test_batch_evaluate_with_qnspsa(self): - """Test batch evaluating with QNSPSA works.""" - ansatz = TwoLocal(2, rotation_blocks=["ry", "rz"], entanglement_blocks="cz") - - wrapped_backend = BasicAer.get_backend("qasm_simulator") - inner_backend = BasicAer.get_backend("statevector_simulator") - - callcount = {"count": 0} - - def wrapped_run(circuits, **kwargs): - kwargs["callcount"]["count"] += 1 - return inner_backend.run(circuits) - - wrapped_backend.run = partial(wrapped_run, callcount=callcount) - - with self.assertWarns(DeprecationWarning): - fidelity = QNSPSA.get_fidelity(ansatz, backend=wrapped_backend) - qnspsa = QNSPSA(fidelity, maxiter=5) - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=ansatz, - optimizer=qnspsa, - max_evals_grouped=100, - quantum_instance=wrapped_backend, - ) - _ = vqe.compute_minimum_eigenvalue(Z ^ Z) - - # 1 calibration + 1 stddev estimation + 1 initial blocking - # + 5 (1 loss + 1 fidelity + 1 blocking) + 1 return loss + 1 VQE eval - expected = 1 + 1 + 1 + 5 * 3 + 1 + 1 - - self.assertEqual(callcount["count"], expected) - - def test_set_ansatz_to_none(self): - """Tests that setting the ansatz to None results in the default behavior""" - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=self.ryrz_wavefunction, - optimizer=L_BFGS_B(), - quantum_instance=self.statevector_simulator, - ) - - vqe.ansatz = None - self.assertIsInstance(vqe.ansatz, RealAmplitudes) - - def test_set_optimizer_to_none(self): - """Tests that setting the optimizer to None results in the default behavior""" - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=self.ryrz_wavefunction, - optimizer=L_BFGS_B(), - quantum_instance=self.statevector_simulator, - ) - - vqe.optimizer = None - self.assertIsInstance(vqe.optimizer, SLSQP) - - def test_optimizer_scipy_callable(self): - """Test passing a SciPy optimizer directly as callable.""" - - with self.assertWarns(DeprecationWarning): - vqe = VQE( - optimizer=partial(scipy_minimize, method="L-BFGS-B", options={"maxiter": 2}), - quantum_instance=self.statevector_simulator, - ) - result = vqe.compute_minimum_eigenvalue(Z) - - self.assertEqual(result.cost_function_evals, 20) - - def test_optimizer_callable(self): - """Test passing a optimizer directly as callable.""" - ansatz = RealAmplitudes(1, reps=1) - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=ansatz, - optimizer=_mock_optimizer, - quantum_instance=self.statevector_simulator, - ) - result = vqe.compute_minimum_eigenvalue(Z) - self.assertTrue(np.all(result.optimal_point == np.zeros(ansatz.num_parameters))) - - def test_aux_operators_list(self): - """Test list-based aux_operators.""" - wavefunction = self.ry_wavefunction - - # Start with an empty list - with self.assertWarns(DeprecationWarning): - vqe = VQE(ansatz=wavefunction, quantum_instance=self.statevector_simulator) - - # Start with an empty list - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=[]) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertIsNone(result.aux_operator_eigenvalues) - - # Go again with two auxiliary operators - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=aux_ops) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][0], 0, places=6) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][1], 0.0) - - # Go again with additional None and zero operators - extra_ops = [*aux_ops, None, 0] - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=extra_ops) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertEqual(len(result.aux_operator_eigenvalues), 4) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][0], 0, places=6) - self.assertEqual(result.aux_operator_eigenvalues[2][0], 0.0) - self.assertEqual(result.aux_operator_eigenvalues[3][0], 0.0) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[2][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[3][1], 0.0) - - def test_aux_operators_dict(self): - """Test dictionary compatibility of aux_operators""" - wavefunction = self.ry_wavefunction - - with self.assertWarns(DeprecationWarning): - vqe = VQE(ansatz=wavefunction, quantum_instance=self.statevector_simulator) - - # Start with an empty dictionary - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators={}) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertIsNone(result.aux_operator_eigenvalues) - - # Go again with two auxiliary operators - with self.assertWarns(DeprecationWarning): - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = {"aux_op1": aux_op1, "aux_op2": aux_op2} - - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=aux_ops) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op2"][0], 0, places=6) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op1"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op2"][1], 0.0) - - # Go again with additional None and zero operators - extra_ops = {**aux_ops, "None_operator": None, "zero_operator": 0} - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=extra_ops) - - self.assertAlmostEqual(result.eigenvalue.real, self.h2_energy, places=6) - self.assertEqual(len(result.aux_operator_eigenvalues), 3) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op1"][0], 2, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op2"][0], 0, places=6) - self.assertEqual(result.aux_operator_eigenvalues["zero_operator"][0], 0.0) - self.assertTrue("None_operator" not in result.aux_operator_eigenvalues.keys()) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op1"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues["aux_op2"][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues["zero_operator"][1], 0.0) - - def test_aux_operator_std_dev_pauli(self): - """Test non-zero standard deviations of aux operators with PauliExpectation.""" - wavefunction = self.ry_wavefunction - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - expectation=PauliExpectation(), - optimizer=COBYLA(maxiter=0), - quantum_instance=self.qasm_simulator, - ) - - with self.assertWarns(DeprecationWarning): - # Go again with two auxiliary operators - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=aux_ops) - - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0], 2.0, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][0], 0.6796875, places=6) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][1], 0.02534712219145965, places=6) - - # Go again with additional None and zero operators - aux_ops = [*aux_ops, None, 0] - - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=aux_ops) - - self.assertEqual(len(result.aux_operator_eigenvalues), 4) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0], 2.0, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][0], 0.57421875, places=6) - self.assertEqual(result.aux_operator_eigenvalues[2][0], 0.0) - self.assertEqual(result.aux_operator_eigenvalues[3][0], 0.0) - # # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1], 0.0) - self.assertAlmostEqual( - result.aux_operator_eigenvalues[1][1], 0.026562146577166837, places=6 - ) - self.assertAlmostEqual(result.aux_operator_eigenvalues[2][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[3][1], 0.0) - - @unittest.skipUnless(optionals.HAS_AER, "Qiskit aer is required to run these tests") - def test_aux_operator_std_dev_aer_pauli(self): - """Test non-zero standard deviations of aux operators with AerPauliExpectation.""" - from qiskit_aer import AerSimulator - - wavefunction = self.ry_wavefunction - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, - expectation=AerPauliExpectation(), - optimizer=COBYLA(maxiter=0), - quantum_instance=QuantumInstance( - backend=AerSimulator(), - shots=1, - seed_simulator=algorithm_globals.random_seed, - seed_transpiler=algorithm_globals.random_seed, - ), - ) - with self.assertWarns(DeprecationWarning): - # Go again with two auxiliary operators - aux_op1 = PauliSumOp.from_list([("II", 2.0)]) - aux_op2 = PauliSumOp.from_list([("II", 0.5), ("ZZ", 0.5), ("YY", 0.5), ("XX", -0.5)]) - aux_ops = [aux_op1, aux_op2] - - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=aux_ops) - - self.assertEqual(len(result.aux_operator_eigenvalues), 2) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0], 2.0, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][0], 0.6698863565455391, places=6) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][1], 0.0, places=6) - - # Go again with additional None and zero operators - aux_ops = [*aux_ops, None, 0] - with self.assertWarns(DeprecationWarning): - result = vqe.compute_minimum_eigenvalue(self.h2_op, aux_operators=aux_ops) - - self.assertEqual(len(result.aux_operator_eigenvalues), 4) - # expectation values - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][0], 2.0, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][0], 0.6036400943063891, places=6) - self.assertEqual(result.aux_operator_eigenvalues[2][0], 0.0) - self.assertEqual(result.aux_operator_eigenvalues[3][0], 0.0) - # standard deviations - self.assertAlmostEqual(result.aux_operator_eigenvalues[0][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[1][1], 0.0, places=6) - self.assertAlmostEqual(result.aux_operator_eigenvalues[2][1], 0.0) - self.assertAlmostEqual(result.aux_operator_eigenvalues[3][1], 0.0) - - def test_2step_transpile(self): - """Test the two-step transpiler pass.""" - # count how often the pass for parameterized circuits is called - pre_counter = LogPass("pre_passmanager") - pre_pass = PassManager(pre_counter) - config = PassManagerConfig(basis_gates=["u3", "cx"]) - pre_pass += level_1_pass_manager(config) - - # ... and the pass for bound circuits - bound_counter = LogPass("bound_pass_manager") - bound_pass = PassManager(bound_counter) - - optimizer = SPSA(maxiter=5, learning_rate=0.01, perturbation=0.01) - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=BasicAer.get_backend("statevector_simulator"), - basis_gates=["u3", "cx"], - pass_manager=pre_pass, - bound_pass_manager=bound_pass, - ) - - with self.assertWarns(DeprecationWarning): - vqe = VQE(optimizer=optimizer, quantum_instance=quantum_instance) - with self.assertLogs(logger, level="INFO") as cm: - _ = vqe.compute_minimum_eigenvalue(Z) - - expected = [ - "pre_passmanager", - "bound_pass_manager", - "bound_pass_manager", - "bound_pass_manager", - "bound_pass_manager", - "bound_pass_manager", - "bound_pass_manager", - "bound_pass_manager", - "bound_pass_manager", - "bound_pass_manager", - "bound_pass_manager", - "bound_pass_manager", - "pre_passmanager", - "bound_pass_manager", - ] - self.assertEqual([record.message for record in cm.records], expected) - - def test_construct_eigenstate_from_optpoint(self): - """Test constructing the eigenstate from the optimal point, if the default ansatz is used.""" - - # use Hamiltonian yielding more than 11 parameters in the default ansatz - with self.assertWarns(DeprecationWarning): - hamiltonian = Z ^ Z ^ Z - - optimizer = SPSA(maxiter=1, learning_rate=0.01, perturbation=0.01) - - with self.assertWarns(DeprecationWarning): - quantum_instance = QuantumInstance( - backend=BasicAer.get_backend("statevector_simulator"), basis_gates=["u3", "cx"] - ) - - with self.assertWarns(DeprecationWarning): - vqe = VQE(optimizer=optimizer, quantum_instance=quantum_instance) - result = vqe.compute_minimum_eigenvalue(hamiltonian) - - optimal_circuit = vqe.ansatz.assign_parameters(result.optimal_point) - self.assertTrue(Statevector(result.eigenstate).equiv(optimal_circuit)) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/__init__.py b/test/python/algorithms/time_evolvers/__init__.py deleted file mode 100644 index fdb172d367f0..000000000000 --- a/test/python/algorithms/time_evolvers/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. diff --git a/test/python/algorithms/time_evolvers/classical_methods/__init__.py b/test/python/algorithms/time_evolvers/classical_methods/__init__.py deleted file mode 100644 index fdb172d367f0..000000000000 --- a/test/python/algorithms/time_evolvers/classical_methods/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. diff --git a/test/python/algorithms/time_evolvers/classical_methods/test_scipy_imaginary_evolver.py b/test/python/algorithms/time_evolvers/classical_methods/test_scipy_imaginary_evolver.py deleted file mode 100644 index 5671bf221284..000000000000 --- a/test/python/algorithms/time_evolvers/classical_methods/test_scipy_imaginary_evolver.py +++ /dev/null @@ -1,183 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test Classical Imaginary Evolver.""" -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import data, ddt, unpack -import numpy as np -from qiskit.algorithms.time_evolvers.time_evolution_problem import TimeEvolutionProblem - -from qiskit.quantum_info.states.statevector import Statevector -from qiskit.quantum_info import SparsePauliOp - -from qiskit import QuantumCircuit -from qiskit.algorithms import SciPyImaginaryEvolver - -from qiskit.opflow import PauliSumOp - - -@ddt -class TestSciPyImaginaryEvolver(QiskitAlgorithmsTestCase): - """Test SciPy Imaginary Evolver.""" - - def create_hamiltonian_lattice(self, num_sites: int) -> SparsePauliOp: - """Creates an Ising Hamiltonian on a lattice.""" - j_const = 0.1 - g_const = -1.0 - - zz_op = ["I" * i + "ZZ" + "I" * (num_sites - i - 2) for i in range(num_sites - 1)] - x_op = ["I" * i + "X" + "I" * (num_sites - i - 1) for i in range(num_sites)] - return SparsePauliOp(zz_op) * j_const + SparsePauliOp(x_op) * g_const - - @data( - (Statevector.from_label("0"), 100, SparsePauliOp("X"), Statevector.from_label("-")), - (Statevector.from_label("0"), 100, SparsePauliOp("-X"), Statevector.from_label("+")), - ) - @unpack - def test_evolve( - self, - initial_state: Statevector, - tau: float, - hamiltonian: SparsePauliOp, - expected_state: Statevector, - ): - """Initializes a classical imaginary evolver and evolves a state to find the ground state. - It compares the solution with the first eigenstate of the hamiltonian. - """ - expected_state_matrix = expected_state.data - - evolution_problem = TimeEvolutionProblem(hamiltonian, tau, initial_state) - classic_evolver = SciPyImaginaryEvolver(num_timesteps=300) - result = classic_evolver.evolve(evolution_problem) - - with self.subTest("Amplitudes"): - np.testing.assert_allclose( - np.absolute(result.evolved_state.data), - np.absolute(expected_state_matrix), - atol=1e-10, - rtol=0, - ) - - with self.subTest("Phases"): - np.testing.assert_allclose( - np.angle(result.evolved_state.data), - np.angle(expected_state_matrix), - atol=1e-10, - rtol=0, - ) - - @data( - ( - Statevector.from_label("0" * 5), - SparsePauliOp.from_sparse_list([("X", [i], 1) for i in range(5)], num_qubits=5), - 5, - ), - (Statevector.from_label("0"), SparsePauliOp("X"), 1), - ) - @unpack - def test_observables( - self, initial_state: Statevector, hamiltonian: SparsePauliOp, nqubits: int - ): - """Tests if the observables are properly evaluated at each timestep.""" - - time_ev = 5.0 - observables = {"Energy": hamiltonian, "Z": SparsePauliOp("Z" * nqubits)} - evolution_problem = TimeEvolutionProblem( - hamiltonian, time_ev, initial_state, aux_operators=observables - ) - - classic_evolver = SciPyImaginaryEvolver(num_timesteps=300) - result = classic_evolver.evolve(evolution_problem) - - z_mean, z_std = result.observables["Z"] - - time_vector = result.times - expected_z = 1 / (np.cosh(time_vector) ** 2 + np.sinh(time_vector) ** 2) - expected_z_std = np.zeros_like(expected_z) - - np.testing.assert_allclose(z_mean, expected_z**nqubits, atol=1e-10, rtol=0) - np.testing.assert_allclose(z_std, expected_z_std, atol=1e-10, rtol=0) - - def test_quantum_circuit_initial_state(self): - """Tests if the system can be evolved with a quantum circuit as an initial state.""" - qc = QuantumCircuit(3) - qc.h(0) - qc.cx(0, range(1, 3)) - - evolution_problem = TimeEvolutionProblem( - hamiltonian=SparsePauliOp("X" * 3), time=1.0, initial_state=qc - ) - classic_evolver = SciPyImaginaryEvolver(num_timesteps=5) - result = classic_evolver.evolve(evolution_problem) - self.assertEqual(result.evolved_state, Statevector(qc)) - - def test_paulisumop_hamiltonian(self): - """Tests if the hamiltonian can be a PauliSumOp""" - with self.assertWarns(DeprecationWarning): - hamiltonian = PauliSumOp.from_list( - [ - ("XI", 1), - ("IX", 1), - ] - ) - observable = PauliSumOp.from_list([("ZZ", 1)]) - evolution_problem = TimeEvolutionProblem( - hamiltonian=hamiltonian, - time=1.0, - initial_state=Statevector.from_label("00"), - aux_operators={"ZZ": observable}, - ) - classic_evolver = SciPyImaginaryEvolver(num_timesteps=5) - result = classic_evolver.evolve(evolution_problem) - expected = 1 / (np.cosh(1.0) ** 2 + np.sinh(1.0) ** 2) - np.testing.assert_almost_equal(result.aux_ops_evaluated["ZZ"][0], expected**2) - - def test_error_time_dependency(self): - """Tests if an error is raised for a time dependent Hamiltonian.""" - evolution_problem = TimeEvolutionProblem( - hamiltonian=SparsePauliOp("X" * 3), - time=1.0, - initial_state=Statevector.from_label("0" * 3), - t_param=0, - ) - classic_evolver = SciPyImaginaryEvolver(num_timesteps=5) - with self.assertRaises(ValueError): - classic_evolver.evolve(evolution_problem) - - def test_no_time_steps(self): - """Tests if the evolver handles some edge cases related to the number of timesteps.""" - evolution_problem = TimeEvolutionProblem( - hamiltonian=SparsePauliOp("X"), - time=1.0, - initial_state=Statevector.from_label("0"), - aux_operators={"Energy": SparsePauliOp("X")}, - ) - - with self.subTest("0 timesteps"): - with self.assertRaises(ValueError): - classic_evolver = SciPyImaginaryEvolver(num_timesteps=0) - classic_evolver.evolve(evolution_problem) - - with self.subTest("1 timestep"): - classic_evolver = SciPyImaginaryEvolver(num_timesteps=1) - result = classic_evolver.evolve(evolution_problem) - np.testing.assert_equal(result.times, np.array([0.0, 1.0])) - - with self.subTest("Negative timesteps"): - with self.assertRaises(ValueError): - classic_evolver = SciPyImaginaryEvolver(num_timesteps=-5) - classic_evolver.evolve(evolution_problem) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/classical_methods/test_scipy_real_evolver.py b/test/python/algorithms/time_evolvers/classical_methods/test_scipy_real_evolver.py deleted file mode 100644 index 40fb1e1e1102..000000000000 --- a/test/python/algorithms/time_evolvers/classical_methods/test_scipy_real_evolver.py +++ /dev/null @@ -1,154 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test Classical Real Evolver.""" -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import data, ddt, unpack -import numpy as np -from qiskit import QuantumCircuit, QuantumRegister -from qiskit.algorithms import SciPyRealEvolver, TimeEvolutionProblem -from qiskit.quantum_info import Statevector, SparsePauliOp - - -def zero(n): - """Auxiliary function to create an initial state on n qubits.""" - qr = QuantumRegister(n) - qc = QuantumCircuit(qr) - return Statevector(qc) - - -def one(n): - """Auxiliary function to create an initial state on n qubits.""" - qr = QuantumRegister(n) - qc = QuantumCircuit(qr) - qc.x(qr) - return Statevector(qc) - - -@ddt -class TestClassicalRealEvolver(QiskitAlgorithmsTestCase): - """Test Classical Real Evolver.""" - - @data( - (one(1), np.pi / 2, SparsePauliOp("X"), -1.0j * zero(1)), - ( - one(1).expand(zero(1)), - np.pi / 2, - SparsePauliOp(["XX", "YY"], [0.5, 0.5]), - -1.0j * zero(1).expand(one(1)), - ), - ( - one(1).expand(zero(1)), - np.pi / 4, - SparsePauliOp(["XX", "YY"], [0.5, 0.5]), - ((one(1).expand(zero(1)) - 1.0j * zero(1).expand(one(1)))) / np.sqrt(2), - ), - (zero(12), np.pi / 2, SparsePauliOp("X" * 12), -1.0j * (one(12))), - ) - @unpack - def test_evolve( - self, - initial_state: Statevector, - time_ev: float, - hamiltonian: SparsePauliOp, - expected_state: Statevector, - ): - """Initializes a classical real evolver and evolves a state.""" - evolution_problem = TimeEvolutionProblem(hamiltonian, time_ev, initial_state) - classic_evolver = SciPyRealEvolver(num_timesteps=1) - result = classic_evolver.evolve(evolution_problem) - - np.testing.assert_allclose( - result.evolved_state.data, - expected_state.data, - atol=1e-10, - rtol=0, - ) - - def test_observables(self): - """Tests if the observables are properly evaluated at each timestep.""" - - initial_state = zero(1) - time_ev = 10.0 - hamiltonian = SparsePauliOp("X") - observables = {"Energy": SparsePauliOp("X"), "Z": SparsePauliOp("Z")} - evolution_problem = TimeEvolutionProblem( - hamiltonian, time_ev, initial_state, aux_operators=observables - ) - classic_evolver = SciPyRealEvolver(num_timesteps=10) - result = classic_evolver.evolve(evolution_problem) - - z_mean, z_std = result.observables["Z"] - - timesteps = z_mean.shape[0] - time_vector = np.linspace(0, time_ev, timesteps) - expected_z = 1 - 2 * (np.sin(time_vector)) ** 2 - expected_z_std = np.zeros_like(expected_z) - - np.testing.assert_allclose(z_mean, expected_z, atol=1e-10, rtol=0) - np.testing.assert_allclose(z_std, expected_z_std, atol=1e-10, rtol=0) - np.testing.assert_equal(time_vector, result.times) - - def test_quantum_circuit_initial_state(self): - """Tests if the system can be evolved with a quantum circuit as an initial state.""" - qc = QuantumCircuit(3) - qc.h(0) - qc.cx(0, range(1, 3)) - - evolution_problem = TimeEvolutionProblem( - hamiltonian=SparsePauliOp("X" * 3), time=2 * np.pi, initial_state=qc - ) - classic_evolver = SciPyRealEvolver(num_timesteps=500) - result = classic_evolver.evolve(evolution_problem) - np.testing.assert_almost_equal( - result.evolved_state.data, - np.array([1, 0, 0, 0, 0, 0, 0, 1]) / np.sqrt(2), - decimal=10, - ) - - def test_error_time_dependency(self): - """Tests if an error is raised for time dependent hamiltonian.""" - evolution_problem = TimeEvolutionProblem( - hamiltonian=SparsePauliOp("X" * 3), time=1.0, initial_state=zero(3), t_param=0 - ) - classic_evolver = SciPyRealEvolver(num_timesteps=5) - with self.assertRaises(ValueError): - classic_evolver.evolve(evolution_problem) - - def test_no_time_steps(self): - """Tests if the evolver handles some edge cases related to the number of timesteps.""" - evolution_problem = TimeEvolutionProblem( - hamiltonian=SparsePauliOp("X"), - time=1.0, - initial_state=zero(1), - aux_operators={"Energy": SparsePauliOp("X")}, - ) - - with self.subTest("0 timesteps"): - with self.assertRaises(ValueError): - classic_evolver = SciPyRealEvolver(num_timesteps=0) - classic_evolver.evolve(evolution_problem) - - with self.subTest("1 timestep"): - classic_evolver = SciPyRealEvolver(num_timesteps=1) - result = classic_evolver.evolve(evolution_problem) - np.testing.assert_equal(result.times, np.array([0.0, 1.0])) - - with self.subTest("Negative timesteps"): - with self.assertRaises(ValueError): - classic_evolver = SciPyRealEvolver(num_timesteps=-5) - classic_evolver.evolve(evolution_problem) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/test_pvqd.py b/test/python/algorithms/time_evolvers/test_pvqd.py deleted file mode 100644 index fc48e7b7159e..000000000000 --- a/test/python/algorithms/time_evolvers/test_pvqd.py +++ /dev/null @@ -1,342 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Tests for PVQD.""" -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase -from functools import partial - -import numpy as np -from ddt import data, ddt, unpack - -from qiskit import QiskitError -from qiskit.algorithms.time_evolvers import TimeEvolutionProblem -from qiskit.algorithms.optimizers import L_BFGS_B, SPSA, GradientDescent, OptimizerResult -from qiskit.algorithms.state_fidelities import ComputeUncompute -from qiskit.algorithms.time_evolvers.pvqd import PVQD -from qiskit.circuit import Gate, Parameter, QuantumCircuit -from qiskit.circuit.library import EfficientSU2 -from qiskit.opflow import PauliSumOp -from qiskit.primitives import Estimator, Sampler -from qiskit.quantum_info import Pauli, SparsePauliOp -from qiskit.test import QiskitTestCase -from qiskit.utils import algorithm_globals - - -# pylint: disable=unused-argument, invalid-name -def gradient_supplied(fun, x0, jac, info): - """A mock optimizer that checks whether the gradient is supported or not.""" - result = OptimizerResult() - result.x = x0 - result.fun = 0 - info["has_gradient"] = jac is not None - - return result - - -class WhatAmI(Gate): - """A custom opaque gate that can be inverted but not decomposed.""" - - def __init__(self, angle): - super().__init__(name="whatami", num_qubits=2, params=[angle]) - - def inverse(self): - return WhatAmI(-self.params[0]) - - -@ddt -class TestPVQD(QiskitAlgorithmsTestCase): - """Tests for the pVQD algorithm.""" - - def setUp(self): - super().setUp() - self.hamiltonian = 0.1 * SparsePauliOp([Pauli("ZZ"), Pauli("IX"), Pauli("XI")]) - self.observable = Pauli("ZZ") - self.ansatz = EfficientSU2(2, reps=1) - self.initial_parameters = np.zeros(self.ansatz.num_parameters) - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 123 - - @data(("ising", True, 2), ("pauli", False, None), ("pauli_sum_op", True, 2)) - @unpack - def test_pvqd(self, hamiltonian_type, gradient, num_timesteps): - """Test a simple evolution.""" - time = 0.02 - - if hamiltonian_type == "ising": - hamiltonian = self.hamiltonian - elif hamiltonian_type == "pauli_sum_op": - with self.assertWarns(DeprecationWarning): - hamiltonian = PauliSumOp(self.hamiltonian) - else: # hamiltonian_type == "pauli": - hamiltonian = Pauli("XX") - - # parse input arguments - if gradient: - optimizer = GradientDescent(maxiter=1) - else: - optimizer = L_BFGS_B(maxiter=1) - - sampler = Sampler() - estimator = Estimator() - fidelity_primitive = ComputeUncompute(sampler) - - # run pVQD keeping track of the energy and the magnetization - pvqd = PVQD( - fidelity_primitive, - self.ansatz, - self.initial_parameters, - estimator, - optimizer=optimizer, - num_timesteps=num_timesteps, - ) - problem = TimeEvolutionProblem( - hamiltonian, time, aux_operators=[hamiltonian, self.observable] - ) - result = pvqd.evolve(problem) - - self.assertTrue(len(result.fidelities) == 3) - self.assertTrue(np.all(result.times == [0.0, 0.01, 0.02])) - self.assertTrue(np.asarray(result.observables).shape == (3, 2)) - num_parameters = self.ansatz.num_parameters - self.assertTrue( - len(result.parameters) == 3 - and np.all([len(params) == num_parameters for params in result.parameters]) - ) - - def test_step(self): - """Test calling the step method directly.""" - sampler = Sampler() - estimator = Estimator() - fidelity_primitive = ComputeUncompute(sampler) - pvqd = PVQD( - fidelity_primitive, - self.ansatz, - self.initial_parameters, - estimator, - optimizer=L_BFGS_B(maxiter=100), - ) - - # perform optimization for a timestep of 0, then the optimal parameters are the current - # ones and the fidelity is 1 - theta_next, fidelity = pvqd.step( - self.hamiltonian, - self.ansatz, - self.initial_parameters, - dt=0.0, - initial_guess=np.zeros_like(self.initial_parameters), - ) - - self.assertTrue(np.allclose(theta_next, self.initial_parameters)) - self.assertAlmostEqual(fidelity, 1) - - def test_get_loss(self): - """Test getting the loss function directly.""" - - sampler = Sampler() - estimator = Estimator() - fidelity_primitive = ComputeUncompute(sampler) - - pvqd = PVQD( - fidelity_primitive, - self.ansatz, - self.initial_parameters, - estimator, - use_parameter_shift=False, - ) - - theta = np.ones(self.ansatz.num_parameters) - loss, gradient = pvqd.get_loss( - self.hamiltonian, self.ansatz, dt=0.0, current_parameters=theta - ) - - displacement = np.arange(self.ansatz.num_parameters) - - with self.subTest(msg="check gradient is None"): - self.assertIsNone(gradient) - - with self.subTest(msg="check loss works"): - self.assertGreater(loss(displacement), 0) - self.assertAlmostEqual(loss(np.zeros_like(theta)), 0) - - def test_invalid_num_timestep(self): - """Test raises if the num_timestep is not positive.""" - sampler = Sampler() - estimator = Estimator() - fidelity_primitive = ComputeUncompute(sampler) - pvqd = PVQD( - fidelity_primitive, - self.ansatz, - self.initial_parameters, - estimator, - optimizer=L_BFGS_B(), - num_timesteps=0, - ) - problem = TimeEvolutionProblem( - self.hamiltonian, time=0.01, aux_operators=[self.hamiltonian, self.observable] - ) - - with self.assertRaises(ValueError): - _ = pvqd.evolve(problem) - - def test_initial_guess_and_observables(self): - """Test doing no optimizations stays at initial guess.""" - initial_guess = np.zeros(self.ansatz.num_parameters) - sampler = Sampler() - estimator = Estimator() - fidelity_primitive = ComputeUncompute(sampler) - - pvqd = PVQD( - fidelity_primitive, - self.ansatz, - self.initial_parameters, - estimator, - optimizer=SPSA(maxiter=0, learning_rate=0.1, perturbation=0.01), - num_timesteps=10, - initial_guess=initial_guess, - ) - problem = TimeEvolutionProblem( - self.hamiltonian, time=0.1, aux_operators=[self.hamiltonian, self.observable] - ) - - result = pvqd.evolve(problem) - - observables = result.aux_ops_evaluated - self.assertEqual(observables[0], 0.1) # expected energy - self.assertEqual(observables[1], 1) # expected magnetization - - def test_zero_parameters(self): - """Test passing an ansatz with zero parameters raises an error.""" - problem = TimeEvolutionProblem(self.hamiltonian, time=0.02) - sampler = Sampler() - fidelity_primitive = ComputeUncompute(sampler) - - pvqd = PVQD( - fidelity_primitive, - QuantumCircuit(2), - np.array([]), - optimizer=SPSA(maxiter=10, learning_rate=0.1, perturbation=0.01), - ) - - with self.assertRaises(QiskitError): - _ = pvqd.evolve(problem) - - def test_initial_state_raises(self): - """Test passing an initial state raises an error for now.""" - initial_state = QuantumCircuit(2) - initial_state.x(0) - - problem = TimeEvolutionProblem( - self.hamiltonian, - time=0.02, - initial_state=initial_state, - ) - - sampler = Sampler() - fidelity_primitive = ComputeUncompute(sampler) - - pvqd = PVQD( - fidelity_primitive, - self.ansatz, - self.initial_parameters, - optimizer=SPSA(maxiter=0, learning_rate=0.1, perturbation=0.01), - ) - - with self.assertRaises(NotImplementedError): - _ = pvqd.evolve(problem) - - def test_aux_ops_raises(self): - """Test passing auxiliary operators with no estimator raises an error.""" - - problem = TimeEvolutionProblem( - self.hamiltonian, time=0.02, aux_operators=[self.hamiltonian, self.observable] - ) - - sampler = Sampler() - fidelity_primitive = ComputeUncompute(sampler) - - pvqd = PVQD( - fidelity_primitive, - self.ansatz, - self.initial_parameters, - optimizer=SPSA(maxiter=0, learning_rate=0.1, perturbation=0.01), - ) - - with self.assertRaises(ValueError): - _ = pvqd.evolve(problem) - - -class TestPVQDUtils(QiskitTestCase): - """Test some utility functions for PVQD.""" - - def setUp(self): - super().setUp() - self.hamiltonian = 0.1 * SparsePauliOp([Pauli("ZZ"), Pauli("IX"), Pauli("XI")]) - self.ansatz = EfficientSU2(2, reps=1) - - def test_gradient_supported(self): - """Test the gradient support is correctly determined.""" - # gradient supported here - wrapped = EfficientSU2(2) # a circuit wrapped into a big instruction - plain = wrapped.decompose() # a plain circuit with already supported instructions - - # gradients not supported on the following circuits - x = Parameter("x") - duplicated = QuantumCircuit(2) - duplicated.rx(x, 0) - duplicated.rx(x, 1) - - needs_chainrule = QuantumCircuit(2) - needs_chainrule.rx(2 * x, 0) - - custom_gate = WhatAmI(x) - unsupported = QuantumCircuit(2) - unsupported.append(custom_gate, [0, 1]) - - tests = [ - (wrapped, True), # tuple: (circuit, gradient support) - (plain, True), - (duplicated, False), - (needs_chainrule, False), - (unsupported, False), - ] - - # used to store the info if a gradient callable is passed into the - # optimizer of not - info = {"has_gradient": None} - optimizer = partial(gradient_supplied, info=info) - - sampler = Sampler() - estimator = Estimator() - fidelity_primitive = ComputeUncompute(sampler) - - pvqd = PVQD( - fidelity=fidelity_primitive, - ansatz=None, - initial_parameters=np.array([]), - estimator=estimator, - optimizer=optimizer, - ) - problem = TimeEvolutionProblem(self.hamiltonian, time=0.01) - for circuit, expected_support in tests: - with self.subTest(circuit=circuit, expected_support=expected_support): - pvqd.ansatz = circuit - pvqd.initial_parameters = np.zeros(circuit.num_parameters) - _ = pvqd.evolve(problem) - self.assertEqual(info["has_gradient"], expected_support) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/test_time_evolution_problem.py b/test/python/algorithms/time_evolvers/test_time_evolution_problem.py deleted file mode 100644 index 1982fb203749..000000000000 --- a/test/python/algorithms/time_evolvers/test_time_evolution_problem.py +++ /dev/null @@ -1,98 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test evolver problem class.""" -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import data, ddt -from numpy.testing import assert_raises -from qiskit import QuantumCircuit -from qiskit.algorithms import TimeEvolutionProblem -from qiskit.quantum_info import Pauli, SparsePauliOp, Statevector -from qiskit.circuit import Parameter -from qiskit.opflow import Y, Z, One, X, Zero, PauliSumOp - - -@ddt -class TestTimeEvolutionProblem(QiskitAlgorithmsTestCase): - """Test evolver problem class.""" - - def test_init_default(self): - """Tests that all default fields are initialized correctly.""" - hamiltonian = Y - time = 2.5 - initial_state = One - - evo_problem = TimeEvolutionProblem(hamiltonian, time, initial_state) - - expected_hamiltonian = Y - expected_time = 2.5 - expected_initial_state = One - expected_aux_operators = None - expected_t_param = None - expected_param_value_dict = None - - self.assertEqual(evo_problem.hamiltonian, expected_hamiltonian) - self.assertEqual(evo_problem.time, expected_time) - self.assertEqual(evo_problem.initial_state, expected_initial_state) - self.assertEqual(evo_problem.aux_operators, expected_aux_operators) - self.assertEqual(evo_problem.t_param, expected_t_param) - self.assertEqual(evo_problem.param_value_map, expected_param_value_dict) - - @data(QuantumCircuit(1), Statevector([1, 0])) - def test_init_all(self, initial_state): - """Tests that all fields are initialized correctly.""" - t_parameter = Parameter("t") - with self.assertWarns(DeprecationWarning): - hamiltonian = t_parameter * Z + Y - time = 2 - aux_operators = [X, Y] - param_value_dict = {t_parameter: 3.2} - - evo_problem = TimeEvolutionProblem( - hamiltonian, - time, - initial_state, - aux_operators, - t_param=t_parameter, - param_value_map=param_value_dict, - ) - - with self.assertWarns(DeprecationWarning): - expected_hamiltonian = Y + t_parameter * Z - expected_time = 2 - expected_type = QuantumCircuit - expected_aux_operators = [X, Y] - expected_t_param = t_parameter - expected_param_value_dict = {t_parameter: 3.2} - - with self.assertWarns(DeprecationWarning): - self.assertEqual(evo_problem.hamiltonian, expected_hamiltonian) - self.assertEqual(evo_problem.time, expected_time) - self.assertEqual(type(evo_problem.initial_state), expected_type) - self.assertEqual(evo_problem.aux_operators, expected_aux_operators) - self.assertEqual(evo_problem.t_param, expected_t_param) - self.assertEqual(evo_problem.param_value_map, expected_param_value_dict) - - def test_validate_params(self): - """Tests expected errors are thrown on parameters mismatch.""" - param_x = Parameter("x") - with self.subTest(msg="Parameter missing in dict."): - with self.assertWarns(DeprecationWarning): - hamiltonian = PauliSumOp(SparsePauliOp([Pauli("X"), Pauli("Y")]), param_x) - evolution_problem = TimeEvolutionProblem(hamiltonian, 2, Zero) - with assert_raises(ValueError): - evolution_problem.validate_params() - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/test_time_evolution_result.py b/test/python/algorithms/time_evolvers/test_time_evolution_result.py deleted file mode 100644 index 26f21ba93627..000000000000 --- a/test/python/algorithms/time_evolvers/test_time_evolution_result.py +++ /dev/null @@ -1,47 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""Class for testing evolution result.""" -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -from qiskit.algorithms import TimeEvolutionResult -from qiskit.opflow import Zero - - -class TestTimeEvolutionResult(QiskitAlgorithmsTestCase): - """Class for testing evolution result and relevant metadata.""" - - def test_init_state(self): - """Tests that a class is initialized correctly with an evolved_state.""" - evolved_state = Zero - evo_result = TimeEvolutionResult(evolved_state=evolved_state) - - expected_state = Zero - expected_aux_ops_evaluated = None - - self.assertEqual(evo_result.evolved_state, expected_state) - self.assertEqual(evo_result.aux_ops_evaluated, expected_aux_ops_evaluated) - - def test_init_observable(self): - """Tests that a class is initialized correctly with an evolved_observable.""" - evolved_state = Zero - evolved_aux_ops_evaluated = [(5j, 5j), (1.0, 8j), (5 + 1j, 6 + 1j)] - evo_result = TimeEvolutionResult(evolved_state, evolved_aux_ops_evaluated) - - expected_state = Zero - expected_aux_ops_evaluated = [(5j, 5j), (1.0, 8j), (5 + 1j, 6 + 1j)] - - self.assertEqual(evo_result.evolved_state, expected_state) - self.assertEqual(evo_result.aux_ops_evaluated, expected_aux_ops_evaluated) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/test_trotter_qrte.py b/test/python/algorithms/time_evolvers/test_trotter_qrte.py deleted file mode 100644 index b8a14f0affeb..000000000000 --- a/test/python/algorithms/time_evolvers/test_trotter_qrte.py +++ /dev/null @@ -1,273 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test TrotterQRTE.""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import ddt, data, unpack -import numpy as np -from scipy.linalg import expm -from numpy.testing import assert_raises - -from qiskit.algorithms.time_evolvers import TimeEvolutionProblem, TrotterQRTE -from qiskit.primitives import Estimator -from qiskit import QuantumCircuit -from qiskit.circuit.library import ZGate -from qiskit.quantum_info import Statevector, Pauli, SparsePauliOp -from qiskit.utils import algorithm_globals -from qiskit.circuit import Parameter -from qiskit.opflow import PauliSumOp, X, MatrixOp -from qiskit.synthesis import SuzukiTrotter, QDrift - - -@ddt -class TestTrotterQRTE(QiskitAlgorithmsTestCase): - """TrotterQRTE tests.""" - - def setUp(self): - super().setUp() - self.seed = 50 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - - @data( - ( - None, - Statevector([0.29192658 - 0.45464871j, 0.70807342 - 0.45464871j]), - ), - ( - SuzukiTrotter(), - Statevector([0.29192658 - 0.84147098j, 0.0 - 0.45464871j]), - ), - ) - @unpack - def test_trotter_qrte_trotter_single_qubit(self, product_formula, expected_state): - """Test for default TrotterQRTE on a single qubit.""" - with self.assertWarns(DeprecationWarning): - operator = PauliSumOp(SparsePauliOp([Pauli("X"), Pauli("Z")])) - initial_state = QuantumCircuit(1) - time = 1 - evolution_problem = TimeEvolutionProblem(operator, time, initial_state) - - trotter_qrte = TrotterQRTE(product_formula=product_formula) - evolution_result_state_circuit = trotter_qrte.evolve(evolution_problem).evolved_state - - np.testing.assert_array_almost_equal( - Statevector.from_instruction(evolution_result_state_circuit).data, expected_state.data - ) - - @data((SparsePauliOp(["X", "Z"]), None), (SparsePauliOp(["X", "Z"]), Parameter("t"))) - @unpack - def test_trotter_qrte_trotter(self, operator, t_param): - """Test for default TrotterQRTE on a single qubit with auxiliary operators.""" - if not t_param is None: - operator = SparsePauliOp(operator.paulis, np.array([t_param, 1])) - - # LieTrotter with 1 rep - aux_ops = [Pauli("X"), Pauli("Y")] - - initial_state = QuantumCircuit(1) - time = 3 - num_timesteps = 2 - evolution_problem = TimeEvolutionProblem( - operator, time, initial_state, aux_ops, t_param=t_param - ) - estimator = Estimator() - - expected_psi, expected_observables_result = self._get_expected_trotter_qrte( - operator, - time, - num_timesteps, - initial_state, - aux_ops, - t_param, - ) - - expected_evolved_state = Statevector(expected_psi) - - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - trotter_qrte = TrotterQRTE(estimator=estimator, num_timesteps=num_timesteps) - evolution_result = trotter_qrte.evolve(evolution_problem) - - np.testing.assert_array_almost_equal( - Statevector.from_instruction(evolution_result.evolved_state).data, - expected_evolved_state.data, - ) - - aux_ops_result = evolution_result.aux_ops_evaluated - expected_aux_ops_result = [ - (expected_observables_result[-1][0], {"variance": 0, "shots": 0}), - (expected_observables_result[-1][1], {"variance": 0, "shots": 0}), - ] - - means = [element[0] for element in aux_ops_result] - expected_means = [element[0] for element in expected_aux_ops_result] - np.testing.assert_array_almost_equal(means, expected_means) - - vars_and_shots = [element[1] for element in aux_ops_result] - expected_vars_and_shots = [element[1] for element in expected_aux_ops_result] - - observables_result = evolution_result.observables - expected_observables_result = [ - [(o, {"variance": 0, "shots": 0}) for o in eor] for eor in expected_observables_result - ] - - means = [sub_element[0] for element in observables_result for sub_element in element] - expected_means = [ - sub_element[0] for element in expected_observables_result for sub_element in element - ] - np.testing.assert_array_almost_equal(means, expected_means) - - for computed, expected in zip(vars_and_shots, expected_vars_and_shots): - self.assertAlmostEqual(computed.pop("variance", 0), expected["variance"], 2) - self.assertEqual(computed.pop("shots", 0), expected["shots"]) - - @data( - ( - PauliSumOp(SparsePauliOp([Pauli("XY"), Pauli("YX")])), - Statevector([-0.41614684 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.90929743 + 0.0j]), - ), - ( - PauliSumOp(SparsePauliOp([Pauli("ZZ"), Pauli("ZI"), Pauli("IZ")])), - Statevector([-0.9899925 - 0.14112001j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j]), - ), - ( - Pauli("YY"), - Statevector([0.54030231 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.84147098j]), - ), - ) - @unpack - def test_trotter_qrte_trotter_two_qubits(self, operator, expected_state): - """Test for TrotterQRTE on two qubits with various types of a Hamiltonian.""" - # LieTrotter with 1 rep - initial_state = QuantumCircuit(2) - evolution_problem = TimeEvolutionProblem(operator, 1, initial_state) - - trotter_qrte = TrotterQRTE() - evolution_result = trotter_qrte.evolve(evolution_problem) - - np.testing.assert_array_almost_equal( - Statevector.from_instruction(evolution_result.evolved_state).data, expected_state.data - ) - - @data( - (QuantumCircuit(1), Statevector([0.23071786 - 0.69436148j, 0.4646314 - 0.49874749j])), - ( - QuantumCircuit(1).compose(ZGate(), [0]), - Statevector([0.23071786 - 0.69436148j, 0.4646314 - 0.49874749j]), - ), - ) - @unpack - def test_trotter_qrte_qdrift(self, initial_state, expected_state): - """Test for TrotterQRTE with QDrift.""" - with self.assertWarns(DeprecationWarning): - operator = PauliSumOp(SparsePauliOp([Pauli("X"), Pauli("Z")])) - time = 1 - evolution_problem = TimeEvolutionProblem(operator, time, initial_state) - - trotter_qrte = TrotterQRTE(product_formula=QDrift(seed=0)) - evolution_result = trotter_qrte.evolve(evolution_problem) - - np.testing.assert_array_almost_equal( - Statevector.from_instruction(evolution_result.evolved_state).data, - expected_state.data, - ) - - @data((Parameter("t"), {}), (None, {Parameter("x"): 2}), (None, None)) - @unpack - def test_trotter_qrte_trotter_param_errors(self, t_param, param_value_dict): - """Test TrotterQRTE with raising errors for parameters.""" - with self.assertWarns(DeprecationWarning): - operator = Parameter("t") * PauliSumOp(SparsePauliOp([Pauli("X")])) + PauliSumOp( - SparsePauliOp([Pauli("Z")]) - ) - initial_state = QuantumCircuit(1) - self._run_error_test(initial_state, operator, None, None, t_param, param_value_dict) - - @data(([Pauli("X"), Pauli("Y")], None)) - @unpack - def test_trotter_qrte_trotter_aux_ops_errors(self, aux_ops, estimator): - """Test TrotterQRTE with raising errors.""" - with self.assertWarns(DeprecationWarning): - operator = PauliSumOp(SparsePauliOp([Pauli("X")])) + PauliSumOp( - SparsePauliOp([Pauli("Z")]) - ) - initial_state = QuantumCircuit(1) - self._run_error_test(initial_state, operator, aux_ops, estimator, None, None) - - @data( - (X, QuantumCircuit(1)), - (MatrixOp([[1, 1], [0, 1]]), QuantumCircuit(1)), - (PauliSumOp(SparsePauliOp([Pauli("X")])) + PauliSumOp(SparsePauliOp([Pauli("Z")])), None), - ( - SparsePauliOp([Pauli("X"), Pauli("Z")], np.array([Parameter("a"), Parameter("b")])), - QuantumCircuit(1), - ), - ) - @unpack - def test_trotter_qrte_trotter_hamiltonian_errors(self, operator, initial_state): - """Test TrotterQRTE with raising errors for evolution problem content.""" - self._run_error_test(initial_state, operator, None, None, None, None) - - @staticmethod - def _run_error_test(initial_state, operator, aux_ops, estimator, t_param, param_value_dict): - time = 1 - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - trotter_qrte = TrotterQRTE(estimator=estimator) - with assert_raises(ValueError): - evolution_problem = TimeEvolutionProblem( - operator, - time, - initial_state, - aux_ops, - t_param=t_param, - param_value_map=param_value_dict, - ) - _ = trotter_qrte.evolve(evolution_problem) - - @staticmethod - def _get_expected_trotter_qrte(operator, time, num_timesteps, init_state, observables, t_param): - """Compute reference values for Trotter evolution via exact matrix exponentiation.""" - dt = time / num_timesteps - observables = [obs.to_matrix() for obs in observables] - - psi = Statevector(init_state).data - if t_param is None: - ops = [Pauli(op).to_matrix() * np.real(coeff) for op, coeff in operator.to_list()] - - observable_results = [] - observable_results.append([np.real(np.conj(psi).dot(obs).dot(psi)) for obs in observables]) - - for n in range(num_timesteps): - if t_param is not None: - time_value = (n + 1) * dt - bound = operator.assign_parameters([time_value]) - ops = [Pauli(op).to_matrix() * np.real(coeff) for op, coeff in bound.to_list()] - for op in ops: - psi = expm(-1j * op * dt).dot(psi) - observable_results.append( - [np.real(np.conj(psi).dot(obs).dot(psi)) for obs in observables] - ) - - return psi, observable_results - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/variational/__init__.py b/test/python/algorithms/time_evolvers/variational/__init__.py deleted file mode 100644 index 26f7536d3514..000000000000 --- a/test/python/algorithms/time_evolvers/variational/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. diff --git a/test/python/algorithms/time_evolvers/variational/solvers/__init__.py b/test/python/algorithms/time_evolvers/variational/solvers/__init__.py deleted file mode 100644 index 26f7536d3514..000000000000 --- a/test/python/algorithms/time_evolvers/variational/solvers/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. diff --git a/test/python/algorithms/time_evolvers/variational/solvers/expected_results/__init__.py b/test/python/algorithms/time_evolvers/variational/solvers/expected_results/__init__.py deleted file mode 100644 index 62ef1f76c6f4..000000000000 --- a/test/python/algorithms/time_evolvers/variational/solvers/expected_results/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""Stores expected results that are lengthy.""" diff --git a/test/python/algorithms/time_evolvers/variational/solvers/expected_results/test_varqte_linear_solver_expected_1.py b/test/python/algorithms/time_evolvers/variational/solvers/expected_results/test_varqte_linear_solver_expected_1.py deleted file mode 100644 index aba6b4006f37..000000000000 --- a/test/python/algorithms/time_evolvers/variational/solvers/expected_results/test_varqte_linear_solver_expected_1.py +++ /dev/null @@ -1,182 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""Stores expected results that are lengthy.""" -expected_metric_res_1 = [ - [ - 2.50000000e-01 + 0.0j, - -3.85185989e-33 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - -3.85185989e-33 + 0.0j, - -3.85185989e-33 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 2.50000000e-01 + 0.0j, - -2.77500000e-17 + 0.0j, - 4.85000000e-17 + 0.0j, - 4.77630626e-32 + 0.0j, - ], - [ - -3.85185989e-33 + 0.0j, - 2.50000000e-01 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - -3.85185989e-33 + 0.0j, - 2.50000000e-01 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - -3.85185989e-33 + 0.0j, - 2.50000000e-01 + 0.0j, - 4.85334346e-32 + 0.0j, - 4.17500000e-17 + 0.0j, - ], - [ - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 0.00000000e00 + 0.0j, - 0.00000000e00 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 0.00000000e00 + 0.0j, - 0.00000000e00 + 0.0j, - -1.38777878e-17 + 0.0j, - -7.00000000e-18 + 0.0j, - 1.38006319e-17 + 0.0j, - -1.39493681e-17 + 0.0j, - ], - [ - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 0.00000000e00 + 0.0j, - 0.00000000e00 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 0.00000000e00 + 0.0j, - 0.00000000e00 + 0.0j, - -1.38777878e-17 + 0.0j, - -7.00000000e-18 + 0.0j, - 1.38006319e-17 + 0.0j, - -1.39493681e-17 + 0.0j, - ], - [ - -3.85185989e-33 + 0.0j, - -3.85185989e-33 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 2.50000000e-01 + 0.0j, - -3.85185989e-33 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - -3.85185989e-33 + 0.0j, - 0.00000000e00 + 0.0j, - 4.85334346e-32 + 0.0j, - -7.00000000e-18 + 0.0j, - ], - [ - -3.85185989e-33 + 0.0j, - 2.50000000e-01 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - -3.85185989e-33 + 0.0j, - 2.50000000e-01 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - -3.85185989e-33 + 0.0j, - 2.50000000e-01 + 0.0j, - 4.85334346e-32 + 0.0j, - 4.17500000e-17 + 0.0j, - ], - [ - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 0.00000000e00 + 0.0j, - 0.00000000e00 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 0.00000000e00 + 0.0j, - 0.00000000e00 + 0.0j, - -1.38777878e-17 + 0.0j, - -7.00000000e-18 + 0.0j, - 1.38006319e-17 + 0.0j, - -1.39493681e-17 + 0.0j, - ], - [ - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 0.00000000e00 + 0.0j, - 0.00000000e00 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 0.00000000e00 + 0.0j, - 0.00000000e00 + 0.0j, - -1.38777878e-17 + 0.0j, - -7.00000000e-18 + 0.0j, - 1.38006319e-17 + 0.0j, - -1.39493681e-17 + 0.0j, - ], - [ - 2.50000000e-01 + 0.0j, - -3.85185989e-33 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - -3.85185989e-33 + 0.0j, - -3.85185989e-33 + 0.0j, - -1.38777878e-17 + 0.0j, - -1.38777878e-17 + 0.0j, - 2.50000000e-01 + 0.0j, - -2.77500000e-17 + 0.0j, - 4.85000000e-17 + 0.0j, - -7.00000000e-18 + 0.0j, - ], - [ - -2.77500000e-17 + 0.0j, - 2.50000000e-01 + 0.0j, - -7.00000000e-18 + 0.0j, - -7.00000000e-18 + 0.0j, - 0.00000000e00 + 0.0j, - 2.50000000e-01 + 0.0j, - -7.00000000e-18 + 0.0j, - -7.00000000e-18 + 0.0j, - -2.77500000e-17 + 0.0j, - 2.50000000e-01 + 0.0j, - 0.00000000e00 + 0.0j, - 4.17500000e-17 + 0.0j, - ], - [ - 4.85000000e-17 + 0.0j, - 4.85334346e-32 + 0.0j, - 1.38006319e-17 + 0.0j, - 1.38006319e-17 + 0.0j, - 4.85334346e-32 + 0.0j, - 4.85334346e-32 + 0.0j, - 1.38006319e-17 + 0.0j, - 1.38006319e-17 + 0.0j, - 4.85000000e-17 + 0.0j, - 0.00000000e00 + 0.0j, - 2.50000000e-01 + 0.0j, - -2.77500000e-17 + 0.0j, - ], - [ - 4.77630626e-32 + 0.0j, - 4.17500000e-17 + 0.0j, - -1.39493681e-17 + 0.0j, - -1.39493681e-17 + 0.0j, - -7.00000000e-18 + 0.0j, - 4.17500000e-17 + 0.0j, - -1.39493681e-17 + 0.0j, - -1.39493681e-17 + 0.0j, - -7.00000000e-18 + 0.0j, - 4.17500000e-17 + 0.0j, - -2.77500000e-17 + 0.0j, - 2.50000000e-01 + 0.0j, - ], -] diff --git a/test/python/algorithms/time_evolvers/variational/solvers/ode/__init__.py b/test/python/algorithms/time_evolvers/variational/solvers/ode/__init__.py deleted file mode 100644 index 26f7536d3514..000000000000 --- a/test/python/algorithms/time_evolvers/variational/solvers/ode/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. diff --git a/test/python/algorithms/time_evolvers/variational/solvers/ode/test_forward_euler_solver.py b/test/python/algorithms/time_evolvers/variational/solvers/ode/test_forward_euler_solver.py deleted file mode 100644 index 802f930931d5..000000000000 --- a/test/python/algorithms/time_evolvers/variational/solvers/ode/test_forward_euler_solver.py +++ /dev/null @@ -1,47 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test Forward Euler solver.""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -import numpy as np -from ddt import ddt, data, unpack -from scipy.integrate import solve_ivp - -from qiskit.algorithms.time_evolvers.variational.solvers.ode.forward_euler_solver import ( - ForwardEulerSolver, -) - - -@ddt -class TestForwardEulerSolver(QiskitAlgorithmsTestCase): - """Test Forward Euler solver.""" - - @unpack - @data((4, 16), (16, 35.52713678800501), (320, 53.261108839604795)) - def test_solve(self, timesteps, expected_result): - """Test Forward Euler solver for a simple ODE.""" - - y0 = [1] - - # pylint: disable=unused-argument - def func(time, y): - return y - - t_span = [0.0, 4.0] - sol1 = solve_ivp(func, t_span, y0, method=ForwardEulerSolver, num_t_steps=timesteps) - np.testing.assert_equal(sol1.y[-1][-1], expected_result) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/variational/solvers/ode/test_ode_function.py b/test/python/algorithms/time_evolvers/variational/solvers/ode/test_ode_function.py deleted file mode 100644 index e680f45063e5..000000000000 --- a/test/python/algorithms/time_evolvers/variational/solvers/ode/test_ode_function.py +++ /dev/null @@ -1,147 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test ODE function generator.""" - -import unittest - -from test.python.algorithms import QiskitAlgorithmsTestCase -import numpy as np -from qiskit.quantum_info import SparsePauliOp -from qiskit.circuit import Parameter -from qiskit.algorithms.time_evolvers.variational.solvers.var_qte_linear_solver import ( - VarQTELinearSolver, -) -from qiskit.algorithms.time_evolvers.variational.solvers.ode.ode_function import ( - OdeFunction, -) -from qiskit.algorithms.time_evolvers.variational import ( - ImaginaryMcLachlanPrinciple, -) -from qiskit.circuit.library import EfficientSU2 - - -class TestOdeFunctionGenerator(QiskitAlgorithmsTestCase): - """Test ODE function generator.""" - - def test_var_qte_ode_function(self): - """Test ODE function generator.""" - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - d = 2 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - # Define a set of initial parameters - parameters = list(ansatz.parameters) - - param_dict = {param: np.pi / 4 for param in parameters} - - var_principle = ImaginaryMcLachlanPrinciple() - - t_param = None - linear_solver = None - linear_solver = VarQTELinearSolver( - var_principle, - observable, - ansatz, - parameters, - t_param, - linear_solver, - ) - - time = 2 - ode_function_generator = OdeFunction(linear_solver, t_param=None, param_dict=param_dict) - - qte_ode_function = ode_function_generator.var_qte_ode_function(time, param_dict.values()) - - expected_qte_ode_function = [ - 0.442145, - -0.022081, - 0.106223, - -0.117468, - 0.251233, - 0.321256, - -0.062728, - -0.036209, - -0.509219, - -0.183459, - -0.050739, - -0.093163, - ] - - np.testing.assert_array_almost_equal(expected_qte_ode_function, qte_ode_function) - - def test_var_qte_ode_function_time_param(self): - """Test ODE function generator with time param.""" - t_param = Parameter("t") - - observable = SparsePauliOp( - ["II", "ZZ", "IZ", "ZI", "YY", "XX"], - np.array([t_param, 0.5716, 0.3435, -0.4347, 0.091, 0.091]), - ) - - d = 2 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - # Define a set of initial parameters - parameters = list(ansatz.parameters) - - param_dict = {param: np.pi / 4 for param in parameters} - - var_principle = ImaginaryMcLachlanPrinciple() - - time = 2 - - linear_solver = None - varqte_linear_solver = VarQTELinearSolver( - var_principle, - observable, - ansatz, - parameters, - t_param, - linear_solver, - ) - ode_function_generator = OdeFunction( - varqte_linear_solver, t_param=t_param, param_dict=param_dict - ) - - qte_ode_function = ode_function_generator.var_qte_ode_function(time, param_dict.values()) - - expected_qte_ode_function = [ - 0.442145, - -0.022081, - 0.106223, - -0.117468, - 0.251233, - 0.321256, - -0.062728, - -0.036209, - -0.509219, - -0.183459, - -0.050739, - -0.093163, - ] - - np.testing.assert_array_almost_equal(expected_qte_ode_function, qte_ode_function, decimal=5) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/variational/solvers/ode/test_var_qte_ode_solver.py b/test/python/algorithms/time_evolvers/variational/solvers/ode/test_var_qte_ode_solver.py deleted file mode 100644 index 4492b3cc9eaf..000000000000 --- a/test/python/algorithms/time_evolvers/variational/solvers/ode/test_var_qte_ode_solver.py +++ /dev/null @@ -1,127 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test solver of ODEs.""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import ddt, data, unpack -import numpy as np - -from qiskit.quantum_info import SparsePauliOp -from qiskit.algorithms.time_evolvers.variational.solvers.ode.forward_euler_solver import ( - ForwardEulerSolver, -) -from qiskit.algorithms.time_evolvers.variational.solvers.var_qte_linear_solver import ( - VarQTELinearSolver, -) -from qiskit.algorithms.time_evolvers.variational.solvers.ode.var_qte_ode_solver import ( - VarQTEOdeSolver, -) -from qiskit.algorithms.time_evolvers.variational.solvers.ode.ode_function import ( - OdeFunction, -) -from qiskit.algorithms.time_evolvers.variational import ( - ImaginaryMcLachlanPrinciple, -) -from qiskit.circuit.library import EfficientSU2 - - -@ddt -class TestVarQTEOdeSolver(QiskitAlgorithmsTestCase): - """Test solver of ODEs.""" - - @data( - ( - "RK45", - [ - -0.30076755873631345, - -0.8032811383782005, - 1.1674108371914734e-15, - 3.2293849116821145e-16, - 2.541585055586039, - 1.155475184255733, - -2.966331417968169e-16, - 9.604292449638343e-17, - ], - ), - ( - ForwardEulerSolver, - [ - -3.2707e-01, - -8.0960e-01, - 3.4323e-16, - 8.9034e-17, - 2.5290e00, - 1.1563e00, - 3.0227e-16, - -2.2769e-16, - ], - ), - ) - @unpack - def test_run_no_backend(self, ode_solver, expected_result): - """Test ODE solver with no backend.""" - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - d = 1 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - # Define a set of initial parameters - parameters = list(ansatz.parameters) - - init_param_values = np.zeros(len(parameters)) - for i in range(ansatz.num_qubits): - init_param_values[-(ansatz.num_qubits + i + 1)] = np.pi / 2 - - param_dict = dict(zip(parameters, init_param_values)) - - var_principle = ImaginaryMcLachlanPrinciple() - - time = 1 - - t_param = None - - linear_solver = None - linear_solver = VarQTELinearSolver( - var_principle, - observable, - ansatz, - parameters, - t_param, - linear_solver, - ) - ode_function_generator = OdeFunction(linear_solver, param_dict, t_param) - - var_qte_ode_solver = VarQTEOdeSolver( - list(param_dict.values()), - ode_function_generator, - ode_solver=ode_solver, - num_timesteps=25, - ) - - result, _, _ = var_qte_ode_solver.run(time) - - np.testing.assert_array_almost_equal(result, expected_result, decimal=4) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/variational/solvers/test_varqte_linear_solver.py b/test/python/algorithms/time_evolvers/variational/solvers/test_varqte_linear_solver.py deleted file mode 100644 index 7b842b95cac0..000000000000 --- a/test/python/algorithms/time_evolvers/variational/solvers/test_varqte_linear_solver.py +++ /dev/null @@ -1,112 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test solver of linear equations.""" - -import unittest -from test.python.algorithms import QiskitAlgorithmsTestCase - -# fmt: off -from test.python.algorithms.time_evolvers.variational.solvers.expected_results.\ - test_varqte_linear_solver_expected_1 import expected_metric_res_1 -# fmt: on - -import numpy as np - -from qiskit.quantum_info import SparsePauliOp -from qiskit.algorithms.time_evolvers.variational import ( - ImaginaryMcLachlanPrinciple, -) -from qiskit.algorithms.time_evolvers.variational.solvers.var_qte_linear_solver import ( - VarQTELinearSolver, -) -from qiskit.circuit.library import EfficientSU2 - - -class TestVarQTELinearSolver(QiskitAlgorithmsTestCase): - """Test solver of linear equations.""" - - def test_solve_lse(self): - """Test SLE solver.""" - - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - d = 2 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - parameters = list(ansatz.parameters) - init_param_values = np.zeros(len(parameters)) - for i in range(ansatz.num_qubits): - init_param_values[-(ansatz.num_qubits + i + 1)] = np.pi / 2 - - param_dict = dict(zip(parameters, init_param_values)) - - var_principle = ImaginaryMcLachlanPrinciple() - t_param = None - linear_solver = None - linear_solver = VarQTELinearSolver( - var_principle, - observable, - ansatz, - parameters, - t_param, - linear_solver, - ) - - nat_grad_res, metric_res, grad_res = linear_solver.solve_lse(param_dict) - - expected_nat_grad_res = [ - 3.43500000e-01, - -2.89800000e-01, - 2.43575264e-16, - 1.31792695e-16, - -9.61200000e-01, - -2.89800000e-01, - 1.27493709e-17, - 1.12587456e-16, - 3.43500000e-01, - -2.89800000e-01, - 3.69914720e-17, - 1.95052083e-17, - ] - - expected_grad_res = [ - (0.17174999999999926 - 0j), - (-0.21735000000000085 + 0j), - (4.114902862895087e-17 - 0j), - (4.114902862895087e-17 - 0j), - (-0.24030000000000012 + 0j), - (-0.21735000000000085 + 0j), - (4.114902862895087e-17 - 0j), - (4.114902862895087e-17 - 0j), - (0.17174999999999918 - 0j), - (-0.21735000000000076 + 0j), - (1.7789936190837538e-17 - 0j), - (-8.319872568662832e-17 + 0j), - ] - - np.testing.assert_array_almost_equal(nat_grad_res, expected_nat_grad_res, decimal=4) - np.testing.assert_array_almost_equal(grad_res, expected_grad_res, decimal=4) - np.testing.assert_array_almost_equal(metric_res, expected_metric_res_1, decimal=4) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/variational/test_var_qite.py b/test/python/algorithms/time_evolvers/variational/test_var_qite.py deleted file mode 100644 index b2e431f09f42..000000000000 --- a/test/python/algorithms/time_evolvers/variational/test_var_qite.py +++ /dev/null @@ -1,333 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test Variational Quantum Imaginary Time Evolution algorithm.""" - -import unittest -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase -from ddt import ddt -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter -from qiskit.algorithms.gradients import LinCombQGT, LinCombEstimatorGradient -from qiskit.primitives import Estimator -from qiskit.quantum_info import SparsePauliOp, Pauli -from qiskit.utils import algorithm_globals -from qiskit.algorithms import TimeEvolutionProblem, VarQITE -from qiskit.algorithms.time_evolvers.variational import ( - ImaginaryMcLachlanPrinciple, -) -from qiskit.circuit.library import EfficientSU2 -from qiskit.quantum_info import Statevector - - -@ddt -class TestVarQITE(QiskitAlgorithmsTestCase): - """Test Variational Quantum Imaginary Time Evolution algorithm.""" - - def setUp(self): - super().setUp() - self.seed = 11 - np.random.seed(self.seed) - - def test_run_d_1_with_aux_ops(self): - """Test VarQITE for d = 1 and t = 1 with evaluating auxiliary operator and the Forward - Euler solver..""" - - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - aux_ops = [Pauli("XX"), Pauli("YZ")] - d = 1 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - parameters = list(ansatz.parameters) - init_param_values = np.zeros(len(parameters)) - for i in range(len(parameters)): - init_param_values[i] = np.pi / 2 - init_param_values[0] = 1 - time = 1 - - evolution_problem = TimeEvolutionProblem(observable, time, aux_operators=aux_ops) - - thetas_expected = [ - 0.87984606025879, - 2.04681975664763, - 2.68980594039104, - 2.75915988512186, - 2.38796546567171, - 1.78144857115127, - 2.13109162826101, - 1.9259609596416, - ] - - thetas_expected_shots = [ - 0.9392668013702317, - 1.8756706968454864, - 2.6915067128662398, - 2.655420131540562, - 2.174687086978046, - 1.6997059390911056, - 1.8056912289547045, - 1.939353810908912, - ] - - with self.subTest(msg="Test exact backend."): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - estimator = Estimator() - qgt = LinCombQGT(estimator) - gradient = LinCombEstimatorGradient(estimator) - var_principle = ImaginaryMcLachlanPrinciple(qgt, gradient) - - var_qite = VarQITE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=25 - ) - evolution_result = var_qite.evolve(evolution_problem) - - aux_ops = evolution_result.aux_ops_evaluated - - parameter_values = evolution_result.parameter_values[-1] - - expected_aux_ops = (-0.2177982985749799, 0.2556790598588627) - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected[i], decimal=2 - ) - - np.testing.assert_array_almost_equal( - [result[0] for result in aux_ops], expected_aux_ops - ) - - with self.subTest(msg="Test shot-based backend."): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - - estimator = Estimator(options={"shots": 4096, "seed": self.seed}) - qgt = LinCombQGT(estimator) - gradient = LinCombEstimatorGradient(estimator) - var_principle = ImaginaryMcLachlanPrinciple(qgt, gradient) - - var_qite = VarQITE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=25 - ) - evolution_result = var_qite.evolve(evolution_problem) - - aux_ops = evolution_result.aux_ops_evaluated - - parameter_values = evolution_result.parameter_values[-1] - - expected_aux_ops = (-0.24629853310903974, 0.2518122871921184) - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected_shots[i], decimal=2 - ) - - np.testing.assert_array_almost_equal( - [result[0] for result in aux_ops], expected_aux_ops - ) - - def test_run_d_1_t_7(self): - """Test VarQITE for d = 1 and t = 7 with RK45 ODE solver.""" - - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - d = 1 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - parameters = list(ansatz.parameters) - init_param_values = np.zeros(len(parameters)) - for i in range(len(parameters)): - init_param_values[i] = np.pi / 2 - init_param_values[0] = 1 - var_principle = ImaginaryMcLachlanPrinciple() - - time = 7 - var_qite = VarQITE( - ansatz, init_param_values, var_principle, ode_solver="RK45", num_timesteps=25 - ) - - thetas_expected = [ - 0.828917365718767, - 1.88481074798033, - 3.14111335991238, - 3.14125849601269, - 2.33768562678401, - 1.78670990729437, - 2.04214275514208, - 2.04009918594422, - ] - - self._test_helper(observable, thetas_expected, time, var_qite, 2) - - def test_run_d_2(self): - """Test VarQITE for d = 2 and t = 1 with RK45 ODE solver.""" - - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - d = 2 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - parameters = list(ansatz.parameters) - init_param_values = np.zeros(len(parameters)) - for i in range(len(parameters)): - init_param_values[i] = np.pi / 4 - - var_principle = ImaginaryMcLachlanPrinciple() - - time = 1 - var_qite = VarQITE( - ansatz, init_param_values, var_principle, ode_solver="RK45", num_timesteps=25 - ) - - thetas_expected = [ - 1.29495364023786, - 1.08970061333559, - 0.667488228710748, - 0.500122687902944, - 1.4377736672043, - 1.22881086103085, - 0.729773048146251, - 1.01698854755226, - 0.050807780587492, - 0.294828474947149, - 0.839305697704923, - 0.663689581255428, - ] - - self._test_helper(observable, thetas_expected, time, var_qite, 4) - - def test_run_d_1_time_dependent(self): - """Test VarQITE for d = 1 and a time-dependent Hamiltonian with the Forward Euler solver.""" - t_param = Parameter("t") - time = 1 - observable = SparsePauliOp(["I", "Z"], np.array([0, t_param])) - - x, y, z = [Parameter(s) for s in "xyz"] - ansatz = QuantumCircuit(1) - ansatz.rz(x, 0) - ansatz.ry(y, 0) - ansatz.rz(z, 0) - - parameters = list(ansatz.parameters) - init_param_values = np.zeros(len(parameters)) - x_val = 0 - y_val = np.pi / 2 - z_val = 0 - - init_param_values[0] = x_val - init_param_values[1] = y_val - init_param_values[2] = z_val - - evolution_problem = TimeEvolutionProblem(observable, time, t_param=t_param) - - thetas_expected = [1.83881002737137e-18, 2.43224994794434, -3.05311331771918e-18] - - thetas_expected_shots = [1.83881002737137e-18, 2.43224994794434, -3.05311331771918e-18] - - state_expected = Statevector([0.34849948 + 0.0j, 0.93730897 + 0.0j]).to_dict() - # the expected final state is Statevector([0.34849948+0.j, 0.93730897+0.j]) - - with self.subTest(msg="Test exact backend."): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - estimator = Estimator() - var_principle = ImaginaryMcLachlanPrinciple() - - var_qite = VarQITE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=100 - ) - evolution_result = var_qite.evolve(evolution_problem) - evolved_state = evolution_result.evolved_state - parameter_values = evolution_result.parameter_values[-1] - - for key, evolved_value in Statevector(evolved_state).to_dict().items(): - # np.allclose works with complex numbers - self.assertTrue(np.allclose(evolved_value, state_expected[key], 1e-02)) - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected[i], decimal=2 - ) - - with self.subTest(msg="Test shot-based backend."): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - - estimator = Estimator(options={"shots": 4 * 4096, "seed": self.seed}) - var_principle = ImaginaryMcLachlanPrinciple() - - var_qite = VarQITE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=100 - ) - - evolution_result = var_qite.evolve(evolution_problem) - - evolved_state = evolution_result.evolved_state - - parameter_values = evolution_result.parameter_values[-1] - - for key, evolved_value in Statevector(evolved_state).to_dict().items(): - # np.allclose works with complex numbers - self.assertTrue(np.allclose(evolved_value, state_expected[key], 1e-02)) - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected_shots[i], decimal=2 - ) - - def _test_helper(self, observable, thetas_expected, time, var_qite, decimal): - evolution_problem = TimeEvolutionProblem(observable, time) - evolution_result = var_qite.evolve(evolution_problem) - parameter_values = evolution_result.parameter_values[-1] - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected[i], decimal=decimal - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/variational/test_var_qrte.py b/test/python/algorithms/time_evolvers/variational/test_var_qrte.py deleted file mode 100644 index e652125728b0..000000000000 --- a/test/python/algorithms/time_evolvers/variational/test_var_qrte.py +++ /dev/null @@ -1,319 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test Variational Quantum Real Time Evolution algorithm.""" - -import unittest -import warnings -from test.python.algorithms import QiskitAlgorithmsTestCase - -from ddt import ddt -import numpy as np - -from qiskit import QuantumCircuit -from qiskit.circuit import Parameter, ParameterVector -from qiskit.algorithms.gradients import LinCombQGT, DerivativeType, LinCombEstimatorGradient -from qiskit.primitives import Estimator -from qiskit.utils import algorithm_globals -from qiskit.quantum_info import SparsePauliOp, Pauli, Statevector -from qiskit.algorithms import TimeEvolutionProblem, VarQRTE -from qiskit.algorithms.time_evolvers.variational import ( - RealMcLachlanPrinciple, -) -from qiskit.circuit.library import EfficientSU2 - - -@ddt -class TestVarQRTE(QiskitAlgorithmsTestCase): - """Test Variational Quantum Real Time Evolution algorithm.""" - - def setUp(self): - super().setUp() - self.seed = 11 - np.random.seed(self.seed) - - def test_time_dependent_hamiltonian(self): - """Simple test case with a time dependent Hamiltonian.""" - t_param = Parameter("t") - hamiltonian = SparsePauliOp(["Z"], np.array(t_param)) - - x = ParameterVector("x", 3) - circuit = QuantumCircuit(1) - circuit.rz(x[0], 0) - circuit.ry(x[1], 0) - circuit.rz(x[2], 0) - - initial_parameters = np.array([0, np.pi / 2, 0]) - - def expected_state(time): - # possible with pen and paper as the Hamiltonian is diagonal - return 1 / np.sqrt(2) * np.array([np.exp(-0.5j * time**2), np.exp(0.5j * time**2)]) - - final_time = 0.75 - evolution_problem = TimeEvolutionProblem(hamiltonian, t_param=t_param, time=final_time) - estimator = Estimator() - varqrte = VarQRTE(circuit, initial_parameters, estimator=estimator) - - result = varqrte.evolve(evolution_problem) - - final_parameters = result.parameter_values[-1] - final_state = Statevector(circuit.assign_parameters(final_parameters)).to_dict() - final_expected_state = expected_state(final_time) - - for key, expected_value in final_state.items(): - self.assertTrue(np.allclose(final_expected_state[int(key)], expected_value, 1e-02)) - - def test_run_d_1_with_aux_ops(self): - """Test VarQRTE for d = 1 and t = 0.1 with evaluating auxiliary operators and the Forward - Euler solver.""" - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - aux_ops = [Pauli("XX"), Pauli("YZ")] - d = 1 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - parameters = list(ansatz.parameters) - init_param_values = np.zeros(len(parameters)) - for i in range(len(parameters)): - init_param_values[i] = np.pi / 2 - init_param_values[0] = 1 - - time = 0.1 - - evolution_problem = TimeEvolutionProblem(observable, time, aux_operators=aux_ops) - - thetas_expected = [ - 0.886841151529636, - 1.53852629218265, - 1.57099556659882, - 1.5889216657174, - 1.5996487153364, - 1.57018939515742, - 1.63950719260698, - 1.53853696496673, - ] - - thetas_expected_shots = [ - 0.886975892820015, - 1.53822607733397, - 1.57058096749141, - 1.59023223608564, - 1.60105707043745, - 1.57018042397236, - 1.64010900210835, - 1.53959523034133, - ] - - with self.subTest(msg="Test exact backend."): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - estimator = Estimator() - qgt = LinCombQGT(estimator) - gradient = LinCombEstimatorGradient(estimator, derivative_type=DerivativeType.IMAG) - var_principle = RealMcLachlanPrinciple(qgt, gradient) - - var_qrte = VarQRTE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=25 - ) - evolution_result = var_qrte.evolve(evolution_problem) - - aux_ops = evolution_result.aux_ops_evaluated - - parameter_values = evolution_result.parameter_values[-1] - - expected_aux_ops = [0.06836996703935797, 0.7711574493422457] - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected[i], decimal=2 - ) - - np.testing.assert_array_almost_equal( - [result[0] for result in aux_ops], expected_aux_ops - ) - - with self.subTest(msg="Test shot-based backend."): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - - estimator = Estimator(options={"shots": 4 * 4096, "seed": self.seed}) - qgt = LinCombQGT(estimator) - gradient = LinCombEstimatorGradient(estimator, derivative_type=DerivativeType.IMAG) - var_principle = RealMcLachlanPrinciple(qgt, gradient) - - var_qrte = VarQRTE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=25 - ) - evolution_result = var_qrte.evolve(evolution_problem) - - aux_ops = evolution_result.aux_ops_evaluated - - parameter_values = evolution_result.parameter_values[-1] - - expected_aux_ops = [ - 0.070436, - 0.777938, - ] - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected_shots[i], decimal=2 - ) - - np.testing.assert_array_almost_equal( - [result[0] for result in aux_ops], expected_aux_ops, decimal=2 - ) - - def test_run_d_2(self): - """Test VarQRTE for d = 2 and t = 1 with RK45 ODE solver.""" - - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - d = 2 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - parameters = list(ansatz.parameters) - init_param_values = np.zeros(len(parameters)) - for i in range(len(parameters)): - init_param_values[i] = np.pi / 4 - estimator = Estimator() - qgt = LinCombQGT(estimator) - gradient = LinCombEstimatorGradient(estimator, derivative_type=DerivativeType.IMAG) - - var_principle = RealMcLachlanPrinciple(qgt, gradient) - - param_dict = dict(zip(parameters, init_param_values)) - - time = 1 - var_qrte = VarQRTE(ansatz, param_dict, var_principle, ode_solver="RK45", num_timesteps=25) - - thetas_expected = [ - 0.348407744196573, - 0.919404626262464, - 1.18189219371626, - 0.771011177789998, - 0.734384256533924, - 0.965289520781899, - 1.14441687204195, - 1.17231927568571, - 1.03014771379412, - 0.867266309056347, - 0.699606368428206, - 0.610788576398685, - ] - - self._test_helper(observable, thetas_expected, time, var_qrte) - - def test_run_d_1_time_dependent(self): - """Test VarQRTE for d = 1 and a time-dependent Hamiltonian with the Forward Euler solver.""" - t_param = Parameter("t") - time = 1 - observable = SparsePauliOp(["I", "Z"], np.array([0, t_param])) - - x, y, z = [Parameter(s) for s in "xyz"] - ansatz = QuantumCircuit(1) - ansatz.rz(x, 0) - ansatz.ry(y, 0) - ansatz.rz(z, 0) - - parameters = list(ansatz.parameters) - init_param_values = np.zeros(len(parameters)) - x_val = 0 - y_val = np.pi / 2 - z_val = 0 - - init_param_values[0] = x_val - init_param_values[1] = y_val - init_param_values[2] = z_val - - evolution_problem = TimeEvolutionProblem(observable, time, t_param=t_param) - - thetas_expected = [1.27675647831902e-18, 1.5707963267949, 0.990000000000001] - - thetas_expected_shots = [0.00534345821469238, 1.56260960200375, 0.990017403734316] - - # the expected final state is Statevector([0.62289306-0.33467034j, 0.62289306+0.33467034j]) - - with self.subTest(msg="Test exact backend."): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - estimator = Estimator() - qgt = LinCombQGT(estimator) - gradient = LinCombEstimatorGradient(estimator, derivative_type=DerivativeType.IMAG) - var_principle = RealMcLachlanPrinciple(qgt, gradient) - - var_qrte = VarQRTE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=100 - ) - evolution_result = var_qrte.evolve(evolution_problem) - - parameter_values = evolution_result.parameter_values[-1] - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected[i], decimal=2 - ) - - with self.subTest(msg="Test shot-based backend."): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = self.seed - - estimator = Estimator(options={"shots": 4 * 4096, "seed": self.seed}) - qgt = LinCombQGT(estimator) - gradient = LinCombEstimatorGradient(estimator, derivative_type=DerivativeType.IMAG) - var_principle = RealMcLachlanPrinciple(qgt, gradient) - - var_qrte = VarQRTE( - ansatz, init_param_values, var_principle, estimator, num_timesteps=100 - ) - - evolution_result = var_qrte.evolve(evolution_problem) - - parameter_values = evolution_result.parameter_values[-1] - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal( - float(parameter_value), thetas_expected_shots[i], decimal=2 - ) - - def _test_helper(self, observable, thetas_expected, time, var_qrte): - evolution_problem = TimeEvolutionProblem(observable, time) - evolution_result = var_qrte.evolve(evolution_problem) - - parameter_values = evolution_result.parameter_values[-1] - - for i, parameter_value in enumerate(parameter_values): - np.testing.assert_almost_equal(float(parameter_value), thetas_expected[i], decimal=4) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/variational/test_var_qte.py b/test/python/algorithms/time_evolvers/variational/test_var_qte.py deleted file mode 100644 index 4b92e4e460d0..000000000000 --- a/test/python/algorithms/time_evolvers/variational/test_var_qte.py +++ /dev/null @@ -1,84 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""Test Variational Quantum Real Time Evolution algorithm.""" - -import unittest - -from test.python.algorithms import QiskitAlgorithmsTestCase -from numpy.testing import assert_raises -from ddt import data, ddt -import numpy as np - -from qiskit.algorithms.time_evolvers.variational.var_qte import VarQTE -from qiskit.circuit import Parameter - - -@ddt -class TestVarQTE(QiskitAlgorithmsTestCase): - """Test Variational Quantum Time Evolution class methods.""" - - def setUp(self): - super().setUp() - self._parameters1 = [Parameter("a"), Parameter("b"), Parameter("c")] - - @data([1.4, 2, 3], np.asarray([1.4, 2, 3])) - def test_create_init_state_param_dict(self, param_values): - """Tests if a correct dictionary is created.""" - expected = dict(zip(self._parameters1, param_values)) - with self.subTest("Parameters values given as a list test."): - result = VarQTE._create_init_state_param_dict(param_values, self._parameters1) - np.testing.assert_equal(result, expected) - with self.subTest("Parameters values given as a dictionary test."): - result = VarQTE._create_init_state_param_dict( - dict(zip(self._parameters1, param_values)), self._parameters1 - ) - np.testing.assert_equal(result, expected) - with self.subTest("Parameters values given as a superset dictionary test."): - expected = dict( - zip( - [self._parameters1[0], self._parameters1[2]], [param_values[0], param_values[2]] - ) - ) - result = VarQTE._create_init_state_param_dict( - dict(zip(self._parameters1, param_values)), - [self._parameters1[0], self._parameters1[2]], - ) - np.testing.assert_equal(result, expected) - - @data([1.4, 2], np.asarray([1.4, 3]), {}, []) - def test_create_init_state_param_dict_errors_list(self, param_values): - """Tests if an error is raised.""" - with assert_raises(ValueError): - _ = VarQTE._create_init_state_param_dict(param_values, self._parameters1) - - @data([1.4, 2], np.asarray([1.4, 3])) - def test_create_init_state_param_dict_errors_subset(self, param_values): - """Tests if an error is raised if subset of parameters provided.""" - param_values_dict = dict(zip([self._parameters1[0], self._parameters1[2]], param_values)) - with assert_raises(ValueError): - _ = VarQTE._create_init_state_param_dict(param_values_dict, self._parameters1) - - @data("s") - def test_create_init_state_param_dict_errors_value(self, param_values): - """Tests if an error is raised if wrong input.""" - with assert_raises(ValueError): - _ = VarQTE._create_init_state_param_dict(param_values, self._parameters1) - - @data(Parameter("x"), 5) - def test_create_init_state_param_dict_errors_type(self, param_values): - """Tests if an error is raised if wrong input type.""" - with assert_raises(TypeError): - _ = VarQTE._create_init_state_param_dict(param_values, self._parameters1) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/variational/variational_principles/__init__.py b/test/python/algorithms/time_evolvers/variational/variational_principles/__init__.py deleted file mode 100644 index 26f7536d3514..000000000000 --- a/test/python/algorithms/time_evolvers/variational/variational_principles/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. diff --git a/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/__init__.py b/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/__init__.py deleted file mode 100644 index 62ef1f76c6f4..000000000000 --- a/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""Stores expected results that are lengthy.""" diff --git a/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected1.py b/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected1.py deleted file mode 100644 index a26cb1b8726b..000000000000 --- a/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected1.py +++ /dev/null @@ -1,182 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""Stores expected results that are lengthy.""" -expected_bound_metric_tensor_1 = [ - [ - 2.50000000e-01 + 0.0j, - 1.59600000e-33 + 0.0j, - 5.90075760e-18 + 0.0j, - -8.49242405e-19 + 0.0j, - 8.83883476e-02 + 0.0j, - 1.33253788e-17 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.40000000e-17 + 0.0j, - -1.41735435e-01 + 0.0j, - 3.12500000e-02 + 0.0j, - 1.00222087e-01 + 0.0j, - -3.12500000e-02 + 0.0j, - ], - [ - 1.59600000e-33 + 0.0j, - 2.50000000e-01 + 0.0j, - 1.34350288e-17 + 0.0j, - 6.43502884e-18 + 0.0j, - -8.83883476e-02 + 0.0j, - 1.25000000e-01 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.25000000e-01 + 0.0j, - -8.45970869e-02 + 0.0j, - 7.54441738e-02 + 0.0j, - 1.48207521e-01 + 0.0j, - 2.00444174e-01 + 0.0j, - ], - [ - 5.90075760e-18 + 0.0j, - 1.34350288e-17 + 0.0j, - 1.25000000e-01 + 0.0j, - -1.38777878e-17 + 0.0j, - -4.41941738e-02 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.19638348e-01 + 0.0j, - 6.25000000e-02 + 0.0j, - -5.14514565e-02 + 0.0j, - 6.89720869e-02 + 0.0j, - 1.04933262e-02 + 0.0j, - -6.89720869e-02 + 0.0j, - ], - [ - -8.49242405e-19 + 0.0j, - 6.43502884e-18 + 0.0j, - -1.38777878e-17 + 0.0j, - 1.25000000e-01 + 0.0j, - -4.41941738e-02 + 0.0j, - -6.25000000e-02 + 0.0j, - 3.12500000e-02 + 0.0j, - 1.25000000e-01 + 0.0j, - 5.14514565e-02 + 0.0j, - -6.89720869e-02 + 0.0j, - 7.81250000e-03 + 0.0j, - 1.94162607e-02 + 0.0j, - ], - [ - 8.83883476e-02 + 0.0j, - -8.83883476e-02 + 0.0j, - -4.41941738e-02 + 0.0j, - -4.41941738e-02 + 0.0j, - 2.34375000e-01 + 0.0j, - -1.10485435e-01 + 0.0j, - -2.02014565e-02 + 0.0j, - -4.41941738e-02 + 0.0j, - 1.49547935e-02 + 0.0j, - -2.24896848e-02 + 0.0j, - -1.42172278e-03 + 0.0j, - -1.23822206e-01 + 0.0j, - ], - [ - 1.33253788e-17 + 0.0j, - 1.25000000e-01 + 0.0j, - 6.25000000e-02 + 0.0j, - -6.25000000e-02 + 0.0j, - -1.10485435e-01 + 0.0j, - 2.18750000e-01 + 0.0j, - -2.68082618e-03 + 0.0j, - -1.59099026e-17 + 0.0j, - -1.57197815e-01 + 0.0j, - 2.53331304e-02 + 0.0j, - 9.82311963e-03 + 0.0j, - 1.06138957e-01 + 0.0j, - ], - [ - 6.25000000e-02 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.19638348e-01 + 0.0j, - 3.12500000e-02 + 0.0j, - -2.02014565e-02 + 0.0j, - -2.68082618e-03 + 0.0j, - 2.23881674e-01 + 0.0j, - 1.37944174e-01 + 0.0j, - -3.78033966e-02 + 0.0j, - 1.58423239e-01 + 0.0j, - 1.34535646e-01 + 0.0j, - -5.49651086e-02 + 0.0j, - ], - [ - 1.40000000e-17 + 0.0j, - 1.25000000e-01 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.25000000e-01 + 0.0j, - -4.41941738e-02 + 0.0j, - -1.59099026e-17 + 0.0j, - 1.37944174e-01 + 0.0j, - 2.50000000e-01 + 0.0j, - -2.10523539e-17 + 0.0j, - 1.15574269e-17 + 0.0j, - 9.75412607e-02 + 0.0j, - 5.71383476e-02 + 0.0j, - ], - [ - -1.41735435e-01 + 0.0j, - -8.45970869e-02 + 0.0j, - -5.14514565e-02 + 0.0j, - 5.14514565e-02 + 0.0j, - 1.49547935e-02 + 0.0j, - -1.57197815e-01 + 0.0j, - -3.78033966e-02 + 0.0j, - -2.10523539e-17 + 0.0j, - 1.95283753e-01 + 0.0j, - -3.82941440e-02 + 0.0j, - -6.11392595e-02 + 0.0j, - -4.51588288e-02 + 0.0j, - ], - [ - 3.12500000e-02 + 0.0j, - 7.54441738e-02 + 0.0j, - 6.89720869e-02 + 0.0j, - -6.89720869e-02 + 0.0j, - -2.24896848e-02 + 0.0j, - 2.53331304e-02 + 0.0j, - 1.58423239e-01 + 0.0j, - 1.15574269e-17 + 0.0j, - -3.82941440e-02 + 0.0j, - 2.17629701e-01 + 0.0j, - 1.32431810e-01 + 0.0j, - -1.91961467e-02 + 0.0j, - ], - [ - 1.00222087e-01 + 0.0j, - 1.48207521e-01 + 0.0j, - 1.04933262e-02 + 0.0j, - 7.81250000e-03 + 0.0j, - -1.42172278e-03 + 0.0j, - 9.82311963e-03 + 0.0j, - 1.34535646e-01 + 0.0j, - 9.75412607e-02 + 0.0j, - -6.11392595e-02 + 0.0j, - 1.32431810e-01 + 0.0j, - 1.81683746e-01 + 0.0j, - 7.28902444e-02 + 0.0j, - ], - [ - -3.12500000e-02 + 0.0j, - 2.00444174e-01 + 0.0j, - -6.89720869e-02 + 0.0j, - 1.94162607e-02 + 0.0j, - -1.23822206e-01 + 0.0j, - 1.06138957e-01 + 0.0j, - -5.49651086e-02 + 0.0j, - 5.71383476e-02 + 0.0j, - -4.51588288e-02 + 0.0j, - -1.91961467e-02 + 0.0j, - 7.28902444e-02 + 0.0j, - 2.38616353e-01 + 0.0j, - ], -] diff --git a/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected2.py b/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected2.py deleted file mode 100644 index 3a46f371787c..000000000000 --- a/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected2.py +++ /dev/null @@ -1,182 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""Stores expected results that are lengthy.""" -expected_bound_metric_tensor_2 = [ - [ - 2.50000000e-01 + 0.0j, - 1.59600000e-33 + 0.0j, - 5.90075760e-18 + 0.0j, - -8.49242405e-19 + 0.0j, - 8.83883476e-02 + 0.0j, - 1.33253788e-17 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.40000000e-17 + 0.0j, - -1.41735435e-01 + 0.0j, - 3.12500000e-02 + 0.0j, - 1.00222087e-01 + 0.0j, - -3.12500000e-02 + 0.0j, - ], - [ - 1.59600000e-33 + 0.0j, - 2.50000000e-01 + 0.0j, - 1.34350288e-17 + 0.0j, - 6.43502884e-18 + 0.0j, - -8.83883476e-02 + 0.0j, - 1.25000000e-01 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.25000000e-01 + 0.0j, - -8.45970869e-02 + 0.0j, - 7.54441738e-02 + 0.0j, - 1.48207521e-01 + 0.0j, - 2.00444174e-01 + 0.0j, - ], - [ - 5.90075760e-18 + 0.0j, - 1.34350288e-17 + 0.0j, - 1.25000000e-01 + 0.0j, - -1.38777878e-17 + 0.0j, - -4.41941738e-02 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.19638348e-01 + 0.0j, - 6.25000000e-02 + 0.0j, - -5.14514565e-02 + 0.0j, - 6.89720869e-02 + 0.0j, - 1.04933262e-02 + 0.0j, - -6.89720869e-02 + 0.0j, - ], - [ - -8.49242405e-19 + 0.0j, - 6.43502884e-18 + 0.0j, - -1.38777878e-17 + 0.0j, - 1.25000000e-01 + 0.0j, - -4.41941738e-02 + 0.0j, - -6.25000000e-02 + 0.0j, - 3.12500000e-02 + 0.0j, - 1.25000000e-01 + 0.0j, - 5.14514565e-02 + 0.0j, - -6.89720869e-02 + 0.0j, - 7.81250000e-03 + 0.0j, - 1.94162607e-02 + 0.0j, - ], - [ - 8.83883476e-02 + 0.0j, - -8.83883476e-02 + 0.0j, - -4.41941738e-02 + 0.0j, - -4.41941738e-02 + 0.0j, - 2.34375000e-01 + 0.0j, - -1.10485435e-01 + 0.0j, - -2.02014565e-02 + 0.0j, - -4.41941738e-02 + 0.0j, - 1.49547935e-02 + 0.0j, - -2.24896848e-02 + 0.0j, - -1.42172278e-03 + 0.0j, - -1.23822206e-01 + 0.0j, - ], - [ - 1.33253788e-17 + 0.0j, - 1.25000000e-01 + 0.0j, - 6.25000000e-02 + 0.0j, - -6.25000000e-02 + 0.0j, - -1.10485435e-01 + 0.0j, - 2.18750000e-01 + 0.0j, - -2.68082618e-03 + 0.0j, - -1.59099026e-17 + 0.0j, - -1.57197815e-01 + 0.0j, - 2.53331304e-02 + 0.0j, - 9.82311963e-03 + 0.0j, - 1.06138957e-01 + 0.0j, - ], - [ - 6.25000000e-02 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.19638348e-01 + 0.0j, - 3.12500000e-02 + 0.0j, - -2.02014565e-02 + 0.0j, - -2.68082618e-03 + 0.0j, - 2.23881674e-01 + 0.0j, - 1.37944174e-01 + 0.0j, - -3.78033966e-02 + 0.0j, - 1.58423239e-01 + 0.0j, - 1.34535646e-01 + 0.0j, - -5.49651086e-02 + 0.0j, - ], - [ - 1.40000000e-17 + 0.0j, - 1.25000000e-01 + 0.0j, - 6.25000000e-02 + 0.0j, - 1.25000000e-01 + 0.0j, - -4.41941738e-02 + 0.0j, - -1.59099026e-17 + 0.0j, - 1.37944174e-01 + 0.0j, - 2.50000000e-01 + 0.0j, - -2.10523539e-17 + 0.0j, - 1.15574269e-17 + 0.0j, - 9.75412607e-02 + 0.0j, - 5.71383476e-02 + 0.0j, - ], - [ - -1.41735435e-01 + 0.0j, - -8.45970869e-02 + 0.0j, - -5.14514565e-02 + 0.0j, - 5.14514565e-02 + 0.0j, - 1.49547935e-02 + 0.0j, - -1.57197815e-01 + 0.0j, - -3.78033966e-02 + 0.0j, - -2.10523539e-17 + 0.0j, - 1.95283753e-01 + 0.0j, - -3.82941440e-02 + 0.0j, - -6.11392595e-02 + 0.0j, - -4.51588288e-02 + 0.0j, - ], - [ - 3.12500000e-02 + 0.0j, - 7.54441738e-02 + 0.0j, - 6.89720869e-02 + 0.0j, - -6.89720869e-02 + 0.0j, - -2.24896848e-02 + 0.0j, - 2.53331304e-02 + 0.0j, - 1.58423239e-01 + 0.0j, - 1.15574269e-17 + 0.0j, - -3.82941440e-02 + 0.0j, - 2.17629701e-01 + 0.0j, - 1.32431810e-01 + 0.0j, - -1.91961467e-02 + 0.0j, - ], - [ - 1.00222087e-01 + 0.0j, - 1.48207521e-01 + 0.0j, - 1.04933262e-02 + 0.0j, - 7.81250000e-03 + 0.0j, - -1.42172278e-03 + 0.0j, - 9.82311963e-03 + 0.0j, - 1.34535646e-01 + 0.0j, - 9.75412607e-02 + 0.0j, - -6.11392595e-02 + 0.0j, - 1.32431810e-01 + 0.0j, - 1.81683746e-01 + 0.0j, - 7.28902444e-02 + 0.0j, - ], - [ - -3.12500000e-02 + 0.0j, - 2.00444174e-01 + 0.0j, - -6.89720869e-02 + 0.0j, - 1.94162607e-02 + 0.0j, - -1.23822206e-01 + 0.0j, - 1.06138957e-01 + 0.0j, - -5.49651086e-02 + 0.0j, - 5.71383476e-02 + 0.0j, - -4.51588288e-02 + 0.0j, - -1.91961467e-02 + 0.0j, - 7.28902444e-02 + 0.0j, - 2.38616353e-01 + 0.0j, - ], -] diff --git a/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected3.py b/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected3.py deleted file mode 100644 index 4790482e0db9..000000000000 --- a/test/python/algorithms/time_evolvers/variational/variational_principles/expected_results/test_imaginary_mc_lachlan_variational_principle_expected3.py +++ /dev/null @@ -1,182 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""Stores expected results that are lengthy.""" -expected_bound_metric_tensor_3 = [ - [ - -1.21000000e-34 + 0.00e00j, - 1.21000000e-34 + 2.50e-19j, - 1.76776695e-01 - 1.00e-18j, - -1.40000000e-17 + 0.00e00j, - -6.25000000e-02 + 0.00e00j, - 8.83883476e-02 - 1.25e-18j, - 1.69194174e-01 + 2.25e-18j, - 8.83883476e-02 - 2.50e-19j, - -7.27633476e-02 + 0.00e00j, - 9.75412607e-02 + 7.50e-19j, - 1.48398042e-02 - 1.75e-18j, - -9.75412607e-02 + 3.75e-18j, - ], - [ - 1.21000000e-34 + 2.50e-19j, - -1.21000000e-34 + 0.00e00j, - 1.10000000e-34 + 2.75e-18j, - 1.76776695e-01 - 2.25e-18j, - -6.25000000e-02 + 0.00e00j, - -8.83883476e-02 + 4.00e-18j, - 4.41941738e-02 - 1.25e-18j, - 1.76776695e-01 - 2.50e-19j, - 7.27633476e-02 - 7.50e-19j, - -9.75412607e-02 - 7.50e-19j, - 1.10485435e-02 - 7.50e-19j, - 2.74587393e-02 + 2.50e-19j, - ], - [ - 1.76776695e-01 - 1.00e-18j, - 1.10000000e-34 + 2.75e-18j, - -1.25000000e-01 + 0.00e00j, - -1.25000000e-01 + 0.00e00j, - -1.06694174e-01 + 1.25e-18j, - -6.25000000e-02 + 1.75e-18j, - -1.01332521e-01 + 7.50e-19j, - 4.67500000e-17 - 7.50e-19j, - 1.75206304e-02 + 5.00e-19j, - -8.57075215e-02 - 1.00e-18j, - -1.63277304e-01 + 1.00e-18j, - -1.56250000e-02 + 0.00e00j, - ], - [ - -1.40000000e-17 + 0.00e00j, - 1.76776695e-01 - 2.25e-18j, - -1.25000000e-01 + 0.00e00j, - -1.25000000e-01 + 0.00e00j, - 1.83058262e-02 - 1.50e-18j, - -1.50888348e-01 - 1.50e-18j, - -1.01332521e-01 + 2.50e-19j, - -8.83883476e-02 - 1.00e-18j, - -2.28822827e-02 - 1.00e-18j, - -1.16957521e-01 + 1.00e-18j, - -1.97208130e-01 + 0.00e00j, - -1.79457521e-01 + 1.25e-18j, - ], - [ - -6.25000000e-02 + 0.00e00j, - -6.25000000e-02 + 0.00e00j, - -1.06694174e-01 + 1.25e-18j, - 1.83058262e-02 - 1.50e-18j, - -1.56250000e-02 + 0.00e00j, - -2.20970869e-02 - 2.00e-18j, - 1.48992717e-01 - 1.00e-18j, - 2.60000000e-17 - 1.50e-18j, - -6.69614673e-02 - 5.00e-19j, - 2.00051576e-01 + 5.00e-19j, - 1.13640168e-01 + 1.25e-18j, - -4.83780325e-02 - 1.00e-18j, - ], - [ - 8.83883476e-02 - 1.25e-18j, - -8.83883476e-02 + 4.00e-18j, - -6.25000000e-02 + 1.75e-18j, - -1.50888348e-01 - 1.50e-18j, - -2.20970869e-02 - 2.00e-18j, - -3.12500000e-02 + 0.00e00j, - -2.85691738e-02 + 4.25e-18j, - 1.76776695e-01 + 0.00e00j, - 5.52427173e-03 + 1.00e-18j, - -1.29346478e-01 + 5.00e-19j, - -4.81004238e-02 + 4.25e-18j, - 5.27918696e-02 + 2.50e-19j, - ], - [ - 1.69194174e-01 + 2.25e-18j, - 4.41941738e-02 - 1.25e-18j, - -1.01332521e-01 + 7.50e-19j, - -1.01332521e-01 + 2.50e-19j, - 1.48992717e-01 - 1.00e-18j, - -2.85691738e-02 + 4.25e-18j, - -2.61183262e-02 + 0.00e00j, - -6.88900000e-33 + 0.00e00j, - 6.62099510e-02 - 1.00e-18j, - -2.90767610e-02 + 1.75e-18j, - -1.24942505e-01 + 0.00e00j, - -1.72430217e-02 + 2.50e-19j, - ], - [ - 8.83883476e-02 - 2.50e-19j, - 1.76776695e-01 - 2.50e-19j, - 4.67500000e-17 - 7.50e-19j, - -8.83883476e-02 - 1.00e-18j, - 2.60000000e-17 - 1.50e-18j, - 1.76776695e-01 + 0.00e00j, - -6.88900000e-33 + 0.00e00j, - -6.88900000e-33 + 0.00e00j, - 1.79457521e-01 - 1.75e-18j, - -5.33470869e-02 + 2.00e-18j, - -9.56456304e-02 + 3.00e-18j, - -1.32582521e-01 + 2.50e-19j, - ], - [ - -7.27633476e-02 + 0.00e00j, - 7.27633476e-02 - 7.50e-19j, - 1.75206304e-02 + 5.00e-19j, - -2.28822827e-02 - 1.00e-18j, - -6.69614673e-02 - 5.00e-19j, - 5.52427173e-03 + 1.00e-18j, - 6.62099510e-02 - 1.00e-18j, - 1.79457521e-01 - 1.75e-18j, - -5.47162473e-02 + 0.00e00j, - -4.20854047e-02 + 4.00e-18j, - -7.75494553e-02 - 2.50e-18j, - -2.49573723e-02 + 7.50e-19j, - ], - [ - 9.75412607e-02 + 7.50e-19j, - -9.75412607e-02 - 7.50e-19j, - -8.57075215e-02 - 1.00e-18j, - -1.16957521e-01 + 1.00e-18j, - 2.00051576e-01 + 5.00e-19j, - -1.29346478e-01 + 5.00e-19j, - -2.90767610e-02 + 1.75e-18j, - -5.33470869e-02 + 2.00e-18j, - -4.20854047e-02 + 4.00e-18j, - -3.23702991e-02 + 0.00e00j, - -4.70257118e-02 + 0.00e00j, - 1.22539288e-01 - 2.25e-18j, - ], - [ - 1.48398042e-02 - 1.75e-18j, - 1.10485435e-02 - 7.50e-19j, - -1.63277304e-01 + 1.00e-18j, - -1.97208130e-01 + 0.00e00j, - 1.13640168e-01 + 1.25e-18j, - -4.81004238e-02 + 4.25e-18j, - -1.24942505e-01 + 0.00e00j, - -9.56456304e-02 + 3.00e-18j, - -7.75494553e-02 - 2.50e-18j, - -4.70257118e-02 + 0.00e00j, - -6.83162540e-02 + 0.00e00j, - -2.78870598e-02 + 0.00e00j, - ], - [ - -9.75412607e-02 + 3.75e-18j, - 2.74587393e-02 + 2.50e-19j, - -1.56250000e-02 + 0.00e00j, - -1.79457521e-01 + 1.25e-18j, - -4.83780325e-02 - 1.00e-18j, - 5.27918696e-02 + 2.50e-19j, - -1.72430217e-02 + 2.50e-19j, - -1.32582521e-01 + 2.50e-19j, - -2.49573723e-02 + 7.50e-19j, - 1.22539288e-01 - 2.25e-18j, - -2.78870598e-02 + 0.00e00j, - -1.13836467e-02 + 0.00e00j, - ], -] diff --git a/test/python/algorithms/time_evolvers/variational/variational_principles/imaginary/__init__.py b/test/python/algorithms/time_evolvers/variational/variational_principles/imaginary/__init__.py deleted file mode 100644 index 26f7536d3514..000000000000 --- a/test/python/algorithms/time_evolvers/variational/variational_principles/imaginary/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. diff --git a/test/python/algorithms/time_evolvers/variational/variational_principles/imaginary/test_imaginary_mc_lachlan_principle.py b/test/python/algorithms/time_evolvers/variational/variational_principles/imaginary/test_imaginary_mc_lachlan_principle.py deleted file mode 100644 index 8bb0f0d7c20d..000000000000 --- a/test/python/algorithms/time_evolvers/variational/variational_principles/imaginary/test_imaginary_mc_lachlan_principle.py +++ /dev/null @@ -1,115 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test imaginary McLachlan's variational principle.""" - -import unittest - -# fmt: off -from test.python.algorithms.time_evolvers.variational.variational_principles.expected_results.\ - test_imaginary_mc_lachlan_variational_principle_expected1 import expected_bound_metric_tensor_1 -# fmt: on -from test.python.algorithms import QiskitAlgorithmsTestCase -import numpy as np - -from qiskit.quantum_info import SparsePauliOp -from qiskit.algorithms.time_evolvers.variational import ( - ImaginaryMcLachlanPrinciple, -) -from qiskit.circuit.library import EfficientSU2 -from qiskit.algorithms.gradients import LinCombEstimatorGradient, DerivativeType -from qiskit.primitives import Estimator - - -class TestImaginaryMcLachlanPrinciple(QiskitAlgorithmsTestCase): - """Test imaginary McLachlan's variational principle.""" - - def test_calc_metric_tensor(self): - """Test calculating a metric tensor.""" - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - d = 2 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - # Define a set of initial parameters - parameters = list(ansatz.parameters) - param_dict = {param: np.pi / 4 for param in parameters} - var_principle = ImaginaryMcLachlanPrinciple() - - bound_metric_tensor = var_principle.metric_tensor(ansatz, list(param_dict.values())) - - np.testing.assert_almost_equal(bound_metric_tensor, expected_bound_metric_tensor_1) - - def test_calc_calc_evolution_gradient(self): - """Test calculating evolution gradient.""" - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - d = 2 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - # Define a set of initial parameters - parameters = list(ansatz.parameters) - param_dict = {param: np.pi / 4 for param in parameters} - var_principle = ImaginaryMcLachlanPrinciple() - - bound_evolution_gradient = var_principle.evolution_gradient( - observable, ansatz, list(param_dict.values()), parameters - ) - - expected_evolution_gradient = [ - (0.19308934095957098 - 1.4e-17j), - (0.007027674650099142 - 0j), - (0.03192524520091862 - 0j), - (-0.06810314606309673 - 1e-18j), - (0.07590371669521798 - 7e-18j), - (0.11891968269385343 + 1.5e-18j), - (-0.0012030273438232639 + 0j), - (-0.049885258804562266 + 1.8500000000000002e-17j), - (-0.20178860797540302 - 5e-19j), - (-0.0052269232310933195 + 1e-18j), - (0.022892905637005266 - 3e-18j), - (-0.022892905637005294 + 3.5e-18j), - ] - - np.testing.assert_almost_equal(bound_evolution_gradient, expected_evolution_gradient) - - def test_gradient_setting(self): - """Test reactions to wrong gradient settings..""" - estimator = Estimator() - gradient = LinCombEstimatorGradient(estimator, derivative_type=DerivativeType.IMAG) - - with self.assertWarns(Warning): - var_principle = ImaginaryMcLachlanPrinciple(gradient=gradient) - - np.testing.assert_equal(var_principle.gradient._derivative_type, DerivativeType.REAL) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/time_evolvers/variational/variational_principles/real/__init__.py b/test/python/algorithms/time_evolvers/variational/variational_principles/real/__init__.py deleted file mode 100644 index 26f7536d3514..000000000000 --- a/test/python/algorithms/time_evolvers/variational/variational_principles/real/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. diff --git a/test/python/algorithms/time_evolvers/variational/variational_principles/real/test_real_mc_lachlan_principle.py b/test/python/algorithms/time_evolvers/variational/variational_principles/real/test_real_mc_lachlan_principle.py deleted file mode 100644 index 5a314979d5de..000000000000 --- a/test/python/algorithms/time_evolvers/variational/variational_principles/real/test_real_mc_lachlan_principle.py +++ /dev/null @@ -1,120 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test real McLachlan's variational principle.""" - -import unittest - -from test.python.algorithms import QiskitAlgorithmsTestCase - -# fmt: off -from test.python.algorithms.time_evolvers.variational.variational_principles.expected_results.\ - test_imaginary_mc_lachlan_variational_principle_expected2 import expected_bound_metric_tensor_2 -# fmt: on -import numpy as np - -from qiskit.quantum_info import SparsePauliOp -from qiskit.algorithms.time_evolvers.variational import ( - RealMcLachlanPrinciple, -) -from qiskit.circuit.library import EfficientSU2 -from qiskit.algorithms.gradients import LinCombEstimatorGradient, DerivativeType -from qiskit.primitives import Estimator - - -class TestRealMcLachlanPrinciple(QiskitAlgorithmsTestCase): - """Test real McLachlan's variational principle.""" - - def test_calc_calc_metric_tensor(self): - """Test calculating a metric tensor.""" - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - d = 2 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - # Define a set of initial parameters - parameters = list(ansatz.parameters) - param_dict = {param: np.pi / 4 for param in parameters} - var_principle = RealMcLachlanPrinciple() - - bound_metric_tensor = var_principle.metric_tensor(ansatz, list(param_dict.values())) - - np.testing.assert_almost_equal( - bound_metric_tensor, expected_bound_metric_tensor_2, decimal=5 - ) - - def test_calc_evolution_gradient(self): - """Test calculating evolution gradient.""" - observable = SparsePauliOp.from_list( - [ - ("II", 0.2252), - ("ZZ", 0.5716), - ("IZ", 0.3435), - ("ZI", -0.4347), - ("YY", 0.091), - ("XX", 0.091), - ] - ) - - d = 2 - ansatz = EfficientSU2(observable.num_qubits, reps=d) - - # Define a set of initial parameters - parameters = list(ansatz.parameters) - param_dict = {param: np.pi / 4 for param in parameters} - var_principle = RealMcLachlanPrinciple() - - bound_evolution_gradient = var_principle.evolution_gradient( - observable, ansatz, list(param_dict.values()), parameters - ) - - expected_evolution_gradient = [ - (-0.04514911474522546 + 4e-18j), - (0.0963123928027075 - 1.5e-18j), - (0.1365347823673539 - 7e-18j), - (0.004969316401057883 - 4.9999999999999996e-18j), - (-0.003843833929692342 - 4.999999999999998e-19j), - (0.07036988622493834 - 7e-18j), - (0.16560609099860682 - 3.5e-18j), - (0.16674183768051887 + 1e-18j), - (-0.03843296670360974 - 6e-18j), - (0.08891074158680243 - 6e-18j), - (0.06425681697616654 + 7e-18j), - (-0.03172376682078948 - 7e-18j), - ] - - np.testing.assert_almost_equal( - bound_evolution_gradient, expected_evolution_gradient, decimal=5 - ) - - def test_gradient_setting(self): - """Test reactions to wrong gradient settings..""" - estimator = Estimator() - gradient = LinCombEstimatorGradient(estimator, derivative_type=DerivativeType.REAL) - - with self.assertWarns(Warning): - var_principle = RealMcLachlanPrinciple(gradient=gradient) - - np.testing.assert_equal(var_principle.gradient._derivative_type, DerivativeType.IMAG) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/algorithms/utils/__init__.py b/test/python/algorithms/utils/__init__.py deleted file mode 100644 index fdb172d367f0..000000000000 --- a/test/python/algorithms/utils/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. diff --git a/test/python/algorithms/utils/test_validate_bounds.py b/test/python/algorithms/utils/test_validate_bounds.py deleted file mode 100644 index e4cc42ad154f..000000000000 --- a/test/python/algorithms/utils/test_validate_bounds.py +++ /dev/null @@ -1,57 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test validate bounds.""" - -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase - -from unittest.mock import Mock - -import numpy as np - -from qiskit.algorithms.utils import validate_bounds -from qiskit.utils import algorithm_globals - - -class TestValidateBounds(QiskitAlgorithmsTestCase): - """Test the ``validate_bounds`` utility function.""" - - def setUp(self): - super().setUp() - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - self.bounds = [(-np.pi / 2, np.pi / 2)] - self.ansatz = Mock() - - def test_with_no_ansatz_bounds(self): - """Test with no ansatz bounds.""" - self.ansatz.num_parameters = 1 - self.ansatz.parameter_bounds = None - bounds = validate_bounds(self.ansatz) - self.assertEqual(bounds, [(None, None)]) - - def test_with_ansatz_bounds(self): - """Test with ansatz bounds.""" - self.ansatz.num_parameters = 1 - self.ansatz.parameter_bounds = self.bounds - bounds = validate_bounds(self.ansatz) - self.assertEqual(bounds, self.bounds) - - def test_with_mismatched_num_params(self): - """Test with a mismatched number of parameters and bounds""" - self.ansatz.num_parameters = 2 - self.ansatz.parameter_bounds = self.bounds - with self.assertRaises(ValueError): - _ = validate_bounds(self.ansatz) diff --git a/test/python/algorithms/utils/test_validate_initial_point.py b/test/python/algorithms/utils/test_validate_initial_point.py deleted file mode 100644 index 28854b485fee..000000000000 --- a/test/python/algorithms/utils/test_validate_initial_point.py +++ /dev/null @@ -1,54 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test validate initial point.""" - -import warnings - -from test.python.algorithms import QiskitAlgorithmsTestCase - -from unittest.mock import Mock - -import numpy as np - -from qiskit.algorithms.utils import validate_initial_point -from qiskit.utils import algorithm_globals - - -class TestValidateInitialPoint(QiskitAlgorithmsTestCase): - """Test the ``validate_initial_point`` utility function.""" - - def setUp(self): - super().setUp() - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=DeprecationWarning) - algorithm_globals.random_seed = 0 - self.ansatz = Mock() - self.ansatz.num_parameters = 1 - - def test_with_no_initial_point_or_bounds(self): - """Test with no user-defined initial point and no ansatz bounds.""" - self.ansatz.parameter_bounds = None - initial_point = validate_initial_point(None, self.ansatz) - np.testing.assert_array_almost_equal(initial_point, [1.721111]) - - def test_with_no_initial_point(self): - """Test with no user-defined initial point with ansatz bounds.""" - self.ansatz.parameter_bounds = [(-np.pi / 2, np.pi / 2)] - initial_point = validate_initial_point(None, self.ansatz) - np.testing.assert_array_almost_equal(initial_point, [0.430278]) - - def test_with_mismatched_params(self): - """Test with mistmatched parameters and bounds..""" - self.ansatz.parameter_bounds = None - with self.assertRaises(ValueError): - _ = validate_initial_point([1.0, 2.0], self.ansatz) From 70f6e155d6ec2ef6b3b8846685b57873b74b6529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Wed, 25 Oct 2023 17:29:02 +0200 Subject: [PATCH 2/9] Remove algorithms from tests --- test/python/opflow/test_gradients.py | 49 +--------------------------- 1 file changed, 1 insertion(+), 48 deletions(-) diff --git a/test/python/opflow/test_gradients.py b/test/python/opflow/test_gradients.py index b0fee1233bf5..fc82475313a0 100644 --- a/test/python/opflow/test_gradients.py +++ b/test/python/opflow/test_gradients.py @@ -21,12 +21,10 @@ from ddt import ddt, data, idata, unpack from qiskit import QuantumCircuit, QuantumRegister, BasicAer -from qiskit.test import slow_test from qiskit.utils import QuantumInstance from qiskit.exceptions import MissingOptionalLibraryError from qiskit.utils import algorithm_globals -from qiskit.algorithms import VQE -from qiskit.algorithms.optimizers import CG + from qiskit.opflow import ( I, X, @@ -1166,51 +1164,6 @@ def test_gradient_wrapper2(self, backend_type, atol): result = grad(value) self.assertTrue(np.allclose(result, correct_values[i], atol=atol)) - @slow_test - def test_vqe(self): - """Test VQE with gradients""" - - method = "lin_comb" - backend = "qasm_simulator" - - with self.assertWarns(DeprecationWarning): - q_instance = QuantumInstance( - BasicAer.get_backend(backend), seed_simulator=79, seed_transpiler=2 - ) - - # Define the Hamiltonian - h2_hamiltonian = ( - -1.05 * (I ^ I) + 0.39 * (I ^ Z) - 0.39 * (Z ^ I) - 0.01 * (Z ^ Z) + 0.18 * (X ^ X) - ) - h2_energy = -1.85727503 - - # Define the Ansatz - wavefunction = QuantumCircuit(2) - params = ParameterVector("theta", length=8) - itr = iter(params) - wavefunction.ry(next(itr), 0) - wavefunction.ry(next(itr), 1) - wavefunction.rz(next(itr), 0) - wavefunction.rz(next(itr), 1) - wavefunction.cx(0, 1) - wavefunction.ry(next(itr), 0) - wavefunction.ry(next(itr), 1) - wavefunction.rz(next(itr), 0) - wavefunction.rz(next(itr), 1) - - # Conjugate Gradient algorithm - optimizer = CG(maxiter=10) - - grad = Gradient(grad_method=method) - - # Gradient callable - with self.assertWarns(DeprecationWarning): - vqe = VQE( - ansatz=wavefunction, optimizer=optimizer, gradient=grad, quantum_instance=q_instance - ) - result = vqe.compute_minimum_eigenvalue(operator=h2_hamiltonian) - np.testing.assert_almost_equal(result.optimal_value, h2_energy, decimal=0) - def test_qfi_overlap_works_with_bound_parameters(self): """Test all QFI methods work if the circuit contains a gate with bound parameters.""" From 78955a71bde3fa3344edeffcfdc62ff184f84a97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Wed, 25 Oct 2023 17:31:19 +0200 Subject: [PATCH 3/9] Remove references from docs --- docs/apidoc/index.rst | 1 - qiskit/primitives/backend_estimator.py | 3 +-- qiskit/primitives/backend_sampler.py | 3 +-- qiskit/providers/__init__.py | 6 +++--- qiskit/utils/__init__.py | 5 ++--- qiskit/utils/mitigation/__init__.py | 2 +- 6 files changed, 8 insertions(+), 12 deletions(-) diff --git a/docs/apidoc/index.rst b/docs/apidoc/index.rst index 60c30286fa8c..e957cabb12f3 100644 --- a/docs/apidoc/index.rst +++ b/docs/apidoc/index.rst @@ -57,5 +57,4 @@ Deprecated Modules .. toctree:: :maxdepth: 1 - algorithms opflow diff --git a/qiskit/primitives/backend_estimator.py b/qiskit/primitives/backend_estimator.py index 2ffe5bb7a989..9e0303ac0b4e 100644 --- a/qiskit/primitives/backend_estimator.py +++ b/qiskit/primitives/backend_estimator.py @@ -98,8 +98,7 @@ class BackendEstimator(BaseEstimator[PrimitiveJob[EstimatorResult]]): (or :class:`~.BackendV1`) object in the :class:`~.BaseEstimator` API. It facilitates using backends that do not provide a native :class:`~.BaseEstimator` implementation in places that work with - :class:`~.BaseEstimator`, such as algorithms in :mod:`qiskit.algorithms` - including :class:`~.qiskit.algorithms.minimum_eigensolvers.VQE`. However, + :class:`~.BaseEstimator`. However, if you're using a provider that has a native implementation of :class:`~.BaseEstimator`, it is a better choice to leverage that native implementation as it will likely include additional optimizations and be diff --git a/qiskit/primitives/backend_sampler.py b/qiskit/primitives/backend_sampler.py index 6510b981b68a..145f3a21aaa2 100644 --- a/qiskit/primitives/backend_sampler.py +++ b/qiskit/primitives/backend_sampler.py @@ -38,8 +38,7 @@ class BackendSampler(BaseSampler[PrimitiveJob[SamplerResult]]): any measurement mitigation, it just computes the probability distribution from the counts. It facilitates using backends that do not provide a native :class:`~.BaseSampler` implementation in places that work with - :class:`~.BaseSampler`, such as algorithms in :mod:`qiskit.algorithms` - including :class:`~.qiskit.algorithms.minimum_eigensolvers.SamplingVQE`. + :class:`~.BaseSampler`. However, if you're using a provider that has a native implementation of :class:`~.BaseSampler`, it is a better choice to leverage that native implementation as it will likely include additional optimizations and be diff --git a/qiskit/providers/__init__.py b/qiskit/providers/__init__.py index d7ec4b21b9fe..dc63b6df705f 100644 --- a/qiskit/providers/__init__.py +++ b/qiskit/providers/__init__.py @@ -144,8 +144,8 @@ backend. It also provides the :meth:`~qiskit.providers.BackendV2.run` method which can run the :class:`~qiskit.circuit.QuantumCircuit` objects and/or :class:`~qiskit.pulse.Schedule` objects. This enables users and other Qiskit -APIs, such as :func:`~qiskit.execute_function.execute` and higher level algorithms in -:mod:`qiskit.algorithms`, to get results from executing circuits on devices in a standard +APIs, such as :func:`~qiskit.execute_function.execute` to get results from +executing circuits on devices in a standard fashion regardless of how the backend is implemented. At a high level the basic steps for writing a provider are: @@ -635,7 +635,7 @@ def status(self): provider-specific :class:`~.Sampler` implementation that leverages the ``M3Mitigation`` class internally to run the circuits and return quasi-probabilities directly from mthree in the result. Doing this would -enable algorithms from :mod:`qiskit.algorithms` to get the best results with +enable algorithms to get the best results with mitigation applied directly from your backends. You can refer to the documentation in :mod:`qiskit.primitives` on how to write custom implementations. Also the built-in implementations: :class:`~.Sampler`, diff --git a/qiskit/utils/__init__.py b/qiskit/utils/__init__.py index 789b8c0da9b0..45200413ba2a 100644 --- a/qiskit/utils/__init__.py +++ b/qiskit/utils/__init__.py @@ -48,9 +48,8 @@ A QuantumInstance holds the Qiskit `backend` as well as a number of compile and runtime parameters controlling circuit compilation and execution. Quantum -:mod:`algorithms ` -are run on a device or simulator by passing a QuantumInstance setup with the desired -backend etc. +algorithms are run on a device or simulator by passing a QuantumInstance setup +with the desired backend etc. Optional Dependency Checkers (:mod:`qiskit.utils.optionals`) diff --git a/qiskit/utils/mitigation/__init__.py b/qiskit/utils/mitigation/__init__.py index c79b107cdc0f..54251715424d 100644 --- a/qiskit/utils/mitigation/__init__.py +++ b/qiskit/utils/mitigation/__init__.py @@ -44,7 +44,7 @@ From these calibrations, it is possible to correct the average results of another experiment of interest. These tools are intended for use solely with the :class:`~qiskit.utils.QuantumInstance` class as part of -:mod:`qiskit.algorithms` and :mod:`qiskit.opflow`. +:mod:`qiskit.opflow`. .. autosummary:: :toctree: ../stubs/ From 6b291ab04015d3e801c3e4f326b522985ea15837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Wed, 25 Oct 2023 17:31:50 +0200 Subject: [PATCH 4/9] Remove references from renos --- .../notes/0.17/prepare-0.17-2ab9429b69e1d25c.yaml | 4 ++-- .../0.18/requirements-updates-6059950dfde3cef6.yaml | 2 +- .../readout-mitigation-classes-2ef175e232d791ae.yaml | 10 +++++----- .../notes/0.20/fix-algorithms-7f1b969e5b2447f9.yaml | 2 +- .../notes/0.22/adapt-vqe-0f71234cb6ec92f8.yaml | 2 +- .../notes/0.22/prepare-0.22-118e15de86d36072.yaml | 2 +- .../project-dynamics-primitives-6003336d0866ca19.yaml | 2 +- ...ed-factorizers-linear-solvers-4631870129749624.yaml | 2 +- .../0.25/deprecate-algorithms-7149dee2da586549.yaml | 8 ++++---- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/releasenotes/notes/0.17/prepare-0.17-2ab9429b69e1d25c.yaml b/releasenotes/notes/0.17/prepare-0.17-2ab9429b69e1d25c.yaml index d4d24a5ae737..bbc724c80b69 100644 --- a/releasenotes/notes/0.17/prepare-0.17-2ab9429b69e1d25c.yaml +++ b/releasenotes/notes/0.17/prepare-0.17-2ab9429b69e1d25c.yaml @@ -2,11 +2,11 @@ prelude: | The Qiskit Terra 0.17.0 includes many new features and bug fixes. The major new feature for this release is the introduction of the - :mod:`qiskit.algorithms` and :mod:`qiskit.opflow` modules which were + ``qiskit.algorithms`` and :mod:`qiskit.opflow` modules which were migrated and adapted from the :mod:`qiskit.aqua` project. features: - | - A new module :mod:`qiskit.algorithms` has been introduced. This module + A new module ``qiskit.algorithms`` has been introduced. This module contains functionality equivalent to what has previously been provided by the :mod:`qiskit.aqua.algorithms` module (which is now deprecated) and provides the building blocks for constructing quantum diff --git a/releasenotes/notes/0.18/requirements-updates-6059950dfde3cef6.yaml b/releasenotes/notes/0.18/requirements-updates-6059950dfde3cef6.yaml index e795de505ebc..36d207f2825b 100644 --- a/releasenotes/notes/0.18/requirements-updates-6059950dfde3cef6.yaml +++ b/releasenotes/notes/0.18/requirements-updates-6059950dfde3cef6.yaml @@ -6,7 +6,7 @@ upgrade: was done because of the wide use of the :class:`~qiskit.circuit.library.PhaseOracle` (which depends on having tweedledum installed) with several algorithms - from :mod:`qiskit.algorithms`. + from ``qiskit.algorithms``. - | The optional extra ``full-featured-simulators`` which could previously used to install ``qiskit-aer`` with something like diff --git a/releasenotes/notes/0.19/readout-mitigation-classes-2ef175e232d791ae.yaml b/releasenotes/notes/0.19/readout-mitigation-classes-2ef175e232d791ae.yaml index 69d60ee1545f..459197257dbe 100644 --- a/releasenotes/notes/0.19/readout-mitigation-classes-2ef175e232d791ae.yaml +++ b/releasenotes/notes/0.19/readout-mitigation-classes-2ef175e232d791ae.yaml @@ -8,7 +8,7 @@ features: on backends with measurement errors. Readout mitigator classes have two main methods: - + * :meth:`~.BaseReadoutMitigator.expectation_value` which computes an mitigated expectation value and standard error of a diagonal operator from a noisy :class:`~qiskit.result.Counts` object. @@ -17,7 +17,7 @@ features: mitigated :class:`~qiskit.result.QuasiDistribution`, including standard error, from a noisy counts object. - Note that currently the :mod:`qiskit.algorithms` module and the + Note that currently the ``qiskit.algorithms`` module and the :class:`~qiskit.utils.QuantumInstance` class still use the legacy mitigators migrated from Qiskit Ignis in :mod:`qiskit.utils.mitigation`. It is planned to upgrade the module to use the new mitigator classes and deprecate the legacy @@ -26,12 +26,12 @@ features: Added the :class:`~qiskit.result.LocalReadoutMitigator` class for performing measurement readout error mitigation of local measurement errors. Local measuerment errors are those that are described by a - tensor-product of single-qubit measurement errors. - + tensor-product of single-qubit measurement errors. + This class can be initialized with a list of :math:`N` single-qubit of measurement error assignment matrices or from a backend using the readout error information in the backend properties. - + Mitigation is implemented using local assignment-matrix inversion which has complexity of :math:`O(2^N)` for :math:`N`-qubit mitigation of :class:`~qiskit.result.QuasiDistribution` and expectation values. diff --git a/releasenotes/notes/0.20/fix-algorithms-7f1b969e5b2447f9.yaml b/releasenotes/notes/0.20/fix-algorithms-7f1b969e5b2447f9.yaml index 3178faff0e7c..06cd8bc15e07 100644 --- a/releasenotes/notes/0.20/fix-algorithms-7f1b969e5b2447f9.yaml +++ b/releasenotes/notes/0.20/fix-algorithms-7f1b969e5b2447f9.yaml @@ -13,7 +13,7 @@ upgrade: fixes: - | The :class:`~.AmplitudeAmplifier` is now correctly available from the root - :mod:`qiskit.algorithms` module directly. Previously it was not included + ``qiskit.algorithms`` module directly. Previously it was not included in the re-exported classes off the root module and was only accessible from ``qiskit.algorithms.amplitude_amplifiers``. Fixed `#7751 `__. diff --git a/releasenotes/notes/0.22/adapt-vqe-0f71234cb6ec92f8.yaml b/releasenotes/notes/0.22/adapt-vqe-0f71234cb6ec92f8.yaml index 4d685f006b33..8d36f57398c9 100644 --- a/releasenotes/notes/0.22/adapt-vqe-0f71234cb6ec92f8.yaml +++ b/releasenotes/notes/0.22/adapt-vqe-0f71234cb6ec92f8.yaml @@ -1,7 +1,7 @@ --- features: - | - Added a new algorithm class, :class:`~.AdaptVQE` to :mod:`qiskit.algorithms` + Added a new algorithm class, :class:`~.AdaptVQE` to ``qiskit.algorithms`` This algorithm uses a :class:`qiskit.algorithms.minimum_eigensolvers.VQE` in combination with a pool of operators from which to build out an :class:`qiskit.circuit.library.EvolvedOperatorAnsatz` adaptively. diff --git a/releasenotes/notes/0.22/prepare-0.22-118e15de86d36072.yaml b/releasenotes/notes/0.22/prepare-0.22-118e15de86d36072.yaml index deec31b207fe..8ca521853987 100644 --- a/releasenotes/notes/0.22/prepare-0.22-118e15de86d36072.yaml +++ b/releasenotes/notes/0.22/prepare-0.22-118e15de86d36072.yaml @@ -16,7 +16,7 @@ prelude: | Additionally, :class:`~.BackendV2` backends can now optionally set custom default plugins to use for the scheduling and translation stages. - * Updated algorithm implementations in :mod:`qiskit.algorithms` that leverage + * Updated algorithm implementations in ``qiskit.algorithms`` that leverage the :mod:`~.primitives` classes that implement the :class:`~.BaseSampler` and :class:`~.BaseEstimator`. diff --git a/releasenotes/notes/0.22/project-dynamics-primitives-6003336d0866ca19.yaml b/releasenotes/notes/0.22/project-dynamics-primitives-6003336d0866ca19.yaml index 6ead6d45db4f..8d0fe0780c4b 100644 --- a/releasenotes/notes/0.22/project-dynamics-primitives-6003336d0866ca19.yaml +++ b/releasenotes/notes/0.22/project-dynamics-primitives-6003336d0866ca19.yaml @@ -1,6 +1,6 @@ features: - | - Added the :class:`~.PVQD` class to the time evolution framework in :mod:`qiskit.algorithms`. + Added the :class:`~.PVQD` class to the time evolution framework in ``qiskit.algorithms``. This class implements the projected Variational Quantum Dynamics (p-VQD) algorithm `Barison et al. `_. diff --git a/releasenotes/notes/0.24/remove-deprecated-factorizers-linear-solvers-4631870129749624.yaml b/releasenotes/notes/0.24/remove-deprecated-factorizers-linear-solvers-4631870129749624.yaml index 330a99ef9f22..9f4447a412a3 100644 --- a/releasenotes/notes/0.24/remove-deprecated-factorizers-linear-solvers-4631870129749624.yaml +++ b/releasenotes/notes/0.24/remove-deprecated-factorizers-linear-solvers-4631870129749624.yaml @@ -3,7 +3,7 @@ upgrade: - | The deprecated modules ``factorizers`` and ``linear_solvers``, containing ``HHL`` and ``Shor`` have been removed from - :mod:`qiskit.algorithms`. These functionalities + ``qiskit.algorithms``. These functionalities were originally deprecated as part of the 0.22.0 release (released on October 13, 2022). You can access the code through the Qiskit Textbook instead: `Linear Solvers (HHL) `_ , diff --git a/releasenotes/notes/0.25/deprecate-algorithms-7149dee2da586549.yaml b/releasenotes/notes/0.25/deprecate-algorithms-7149dee2da586549.yaml index 4edaefe8bb7c..7b23c66a943b 100644 --- a/releasenotes/notes/0.25/deprecate-algorithms-7149dee2da586549.yaml +++ b/releasenotes/notes/0.25/deprecate-algorithms-7149dee2da586549.yaml @@ -1,23 +1,23 @@ --- deprecations: - | - The :mod:`qiskit.algorithms` module has been deprecated and will be removed + The ``qiskit.algorithms`` module has been deprecated and will be removed in a future release. It has been superseded by a new standalone library ``qiskit-algorithms`` which can be found on PyPi or on Github here: https://github.com/qiskit-community/qiskit-algorithms - The :mod:`qiskit.algorithms` will continue to work as before and bug fixes + The ``qiskit.algorithms`` module will continue to work as before and bug fixes will be made to module until its future removal, but active development of new features has moved to the new package. - If you're relying on :mod:`qiskit.algorithms` you should update your + If you're relying on ``qiskit.algorithms`` you should update your requirements to also include ``qiskit-algorithms`` and update the imports from ``qiskit.algorithms`` to ``qiskit_algorithms``. Please note that this new package does not include already deprecated algorithms code, including ``opflow`` and ``QuantumInstance``-based algorithms. If you have not yet migrated from ``QuantumInstance``-based to primitives-based algorithms, you should follow the migration guidelines in https://qisk.it/algo_migration. - The decision to migrate the :mod:`~.algorithms` module to a + The decision to migrate the ``qiskit.algorithms`` module to a separate package was made to clarify the purpose Qiskit and make a distinction between the tools and libraries built on top of it. From 303d6969c7479612ca56fca7d32bfa3a9b9f1534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Tue, 31 Oct 2023 11:41:18 +0100 Subject: [PATCH 5/9] Remove optimizer references from AQC --- qiskit/transpiler/synthesis/aqc/aqc.py | 17 +---------------- qiskit/transpiler/synthesis/aqc/aqc_plugin.py | 4 ++-- test/python/transpiler/aqc/test_aqc.py | 10 ---------- 3 files changed, 3 insertions(+), 28 deletions(-) diff --git a/qiskit/transpiler/synthesis/aqc/aqc.py b/qiskit/transpiler/synthesis/aqc/aqc.py index 905116cd2a6c..4ced39a7e4ac 100644 --- a/qiskit/transpiler/synthesis/aqc/aqc.py +++ b/qiskit/transpiler/synthesis/aqc/aqc.py @@ -20,9 +20,7 @@ import numpy as np from scipy.optimize import OptimizeResult, minimize -from qiskit.algorithms.optimizers import Optimizer from qiskit.quantum_info import Operator -from qiskit.utils.deprecation import deprecate_arg from .approximate import ApproximateCircuit, ApproximatingObjective @@ -102,19 +100,9 @@ class AQC: also allocates a number of temporary memory buffers comparable in size to the target matrix. """ - @deprecate_arg( - "optimizer", - deprecation_description=( - "Setting the `optimizer` argument to an instance " - "of `qiskit.algorithms.optimizers.Optimizer` " - ), - additional_msg=("Please, submit a callable that follows the `Minimizer` protocol instead."), - predicate=lambda optimizer: isinstance(optimizer, Optimizer), - since="0.45.0", - ) def __init__( self, - optimizer: Minimizer | Optimizer | None = None, + optimizer: Minimizer | None = None, seed: int | None = None, ): """ @@ -128,9 +116,6 @@ def __init__( self._optimizer = optimizer or partial( minimize, args=(), method="L-BFGS-B", options={"maxiter": 1000} ) - # temporary fix -> remove after deprecation period of Optimizer - if isinstance(self._optimizer, Optimizer): - self._optimizer = self._optimizer.minimize self._seed = seed diff --git a/qiskit/transpiler/synthesis/aqc/aqc_plugin.py b/qiskit/transpiler/synthesis/aqc/aqc_plugin.py index 93403a64f81c..0fa153566557 100644 --- a/qiskit/transpiler/synthesis/aqc/aqc_plugin.py +++ b/qiskit/transpiler/synthesis/aqc/aqc_plugin.py @@ -44,8 +44,8 @@ class AQCSynthesisPlugin(UnitarySynthesisPlugin): depth of the CNOT-network, i.e. the number of layers, where each layer consists of a single CNOT-block. - optimizer (:class:`~qiskit.algorithms.optimizers.Optimizer`) - An instance of optimizer to be used in the optimization process. + optimizer (:class:`~.Minimizer`) + An implementation of the ``Minimizer`` protocol to be used in the optimization process. seed (int) A random seed. diff --git a/test/python/transpiler/aqc/test_aqc.py b/test/python/transpiler/aqc/test_aqc.py index 885fe4c1fc13..c54de8c21dc7 100644 --- a/test/python/transpiler/aqc/test_aqc.py +++ b/test/python/transpiler/aqc/test_aqc.py @@ -21,7 +21,6 @@ import numpy as np from scipy.optimize import minimize -from qiskit.algorithms.optimizers import L_BFGS_B from qiskit.quantum_info import Operator from qiskit.test import QiskitTestCase from qiskit.transpiler.synthesis.aqc.aqc import AQC @@ -67,15 +66,6 @@ def test_aqc(self, uses_default): error = 0.5 * (np.linalg.norm(approx_matrix - ORIGINAL_CIRCUIT, "fro") ** 2) self.assertLess(error, 1e-3) - def test_aqc_deprecation(self): - """Tests that AQC raises deprecation warning.""" - - seed = 12345 - optimizer = L_BFGS_B(maxiter=200) - - with self.assertRaises(DeprecationWarning): - _ = AQC(optimizer=optimizer, seed=seed) - def test_aqc_fastgrad(self): """ Tests AQC on a MCX circuit/matrix with random initial guess using From b43165f478d3b3d337cb526da128ba2ee8dda8a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 6 Nov 2023 11:14:16 +0100 Subject: [PATCH 6/9] Remove algorithms API doc --- docs/apidoc/algorithms.rst | 6 ------ docs/index.rst | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 docs/apidoc/algorithms.rst diff --git a/docs/apidoc/algorithms.rst b/docs/apidoc/algorithms.rst deleted file mode 100644 index 25d1bd4a412b..000000000000 --- a/docs/apidoc/algorithms.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _qiskit-algorithms: - -.. automodule:: qiskit.algorithms - :no-members: - :no-inherited-members: - :no-special-members: diff --git a/docs/index.rst b/docs/index.rst index ef0b28d777b0..b82e864ccfca 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,7 +3,7 @@ Qiskit |version| documentation ############################## Qiskit is open-source software for working with quantum computers -at the level of circuits, pulses, and algorithms. +at the level of circuits and pulses. The central goal of Qiskit is to build a software stack that makes it easy for anyone to use quantum computers, regardless of their skill level or From 738656bb85cb27c7e44a0367e20329f99619c0ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 6 Nov 2023 14:53:56 +0100 Subject: [PATCH 7/9] Add release note --- .../notes/remove-qiskit-algorithms-a43541fe24b72208.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 releasenotes/notes/remove-qiskit-algorithms-a43541fe24b72208.yaml diff --git a/releasenotes/notes/remove-qiskit-algorithms-a43541fe24b72208.yaml b/releasenotes/notes/remove-qiskit-algorithms-a43541fe24b72208.yaml new file mode 100644 index 000000000000..700492a75090 --- /dev/null +++ b/releasenotes/notes/remove-qiskit-algorithms-a43541fe24b72208.yaml @@ -0,0 +1,9 @@ +--- +upgrade: + - | + The ``qiskit.algorithms`` module has been removed, following its deprecation in + Qiskit 0.44. The code has been migrated to a standalone library (``qiskit_algorithms``) + and can be found on PyPi or `GitHub `_. + The decision to migrate the algorithms module to a separate package + was made to clarify the purpose Qiskit and make a distinction between the tools + and libraries built on top of it. From 5ccd0ab97af213acdebd9a314ec0494848f865c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 6 Nov 2023 15:00:10 +0100 Subject: [PATCH 8/9] Revert "Remove references from renos" This reverts commit 6b291ab04015d3e801c3e4f326b522985ea15837. --- .../notes/0.17/prepare-0.17-2ab9429b69e1d25c.yaml | 4 ++-- .../0.18/requirements-updates-6059950dfde3cef6.yaml | 2 +- .../readout-mitigation-classes-2ef175e232d791ae.yaml | 10 +++++----- .../notes/0.20/fix-algorithms-7f1b969e5b2447f9.yaml | 2 +- .../notes/0.22/adapt-vqe-0f71234cb6ec92f8.yaml | 2 +- .../notes/0.22/prepare-0.22-118e15de86d36072.yaml | 2 +- .../project-dynamics-primitives-6003336d0866ca19.yaml | 2 +- ...ed-factorizers-linear-solvers-4631870129749624.yaml | 2 +- .../0.25/deprecate-algorithms-7149dee2da586549.yaml | 8 ++++---- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/releasenotes/notes/0.17/prepare-0.17-2ab9429b69e1d25c.yaml b/releasenotes/notes/0.17/prepare-0.17-2ab9429b69e1d25c.yaml index bbc724c80b69..d4d24a5ae737 100644 --- a/releasenotes/notes/0.17/prepare-0.17-2ab9429b69e1d25c.yaml +++ b/releasenotes/notes/0.17/prepare-0.17-2ab9429b69e1d25c.yaml @@ -2,11 +2,11 @@ prelude: | The Qiskit Terra 0.17.0 includes many new features and bug fixes. The major new feature for this release is the introduction of the - ``qiskit.algorithms`` and :mod:`qiskit.opflow` modules which were + :mod:`qiskit.algorithms` and :mod:`qiskit.opflow` modules which were migrated and adapted from the :mod:`qiskit.aqua` project. features: - | - A new module ``qiskit.algorithms`` has been introduced. This module + A new module :mod:`qiskit.algorithms` has been introduced. This module contains functionality equivalent to what has previously been provided by the :mod:`qiskit.aqua.algorithms` module (which is now deprecated) and provides the building blocks for constructing quantum diff --git a/releasenotes/notes/0.18/requirements-updates-6059950dfde3cef6.yaml b/releasenotes/notes/0.18/requirements-updates-6059950dfde3cef6.yaml index 36d207f2825b..e795de505ebc 100644 --- a/releasenotes/notes/0.18/requirements-updates-6059950dfde3cef6.yaml +++ b/releasenotes/notes/0.18/requirements-updates-6059950dfde3cef6.yaml @@ -6,7 +6,7 @@ upgrade: was done because of the wide use of the :class:`~qiskit.circuit.library.PhaseOracle` (which depends on having tweedledum installed) with several algorithms - from ``qiskit.algorithms``. + from :mod:`qiskit.algorithms`. - | The optional extra ``full-featured-simulators`` which could previously used to install ``qiskit-aer`` with something like diff --git a/releasenotes/notes/0.19/readout-mitigation-classes-2ef175e232d791ae.yaml b/releasenotes/notes/0.19/readout-mitigation-classes-2ef175e232d791ae.yaml index 459197257dbe..69d60ee1545f 100644 --- a/releasenotes/notes/0.19/readout-mitigation-classes-2ef175e232d791ae.yaml +++ b/releasenotes/notes/0.19/readout-mitigation-classes-2ef175e232d791ae.yaml @@ -8,7 +8,7 @@ features: on backends with measurement errors. Readout mitigator classes have two main methods: - + * :meth:`~.BaseReadoutMitigator.expectation_value` which computes an mitigated expectation value and standard error of a diagonal operator from a noisy :class:`~qiskit.result.Counts` object. @@ -17,7 +17,7 @@ features: mitigated :class:`~qiskit.result.QuasiDistribution`, including standard error, from a noisy counts object. - Note that currently the ``qiskit.algorithms`` module and the + Note that currently the :mod:`qiskit.algorithms` module and the :class:`~qiskit.utils.QuantumInstance` class still use the legacy mitigators migrated from Qiskit Ignis in :mod:`qiskit.utils.mitigation`. It is planned to upgrade the module to use the new mitigator classes and deprecate the legacy @@ -26,12 +26,12 @@ features: Added the :class:`~qiskit.result.LocalReadoutMitigator` class for performing measurement readout error mitigation of local measurement errors. Local measuerment errors are those that are described by a - tensor-product of single-qubit measurement errors. - + tensor-product of single-qubit measurement errors. + This class can be initialized with a list of :math:`N` single-qubit of measurement error assignment matrices or from a backend using the readout error information in the backend properties. - + Mitigation is implemented using local assignment-matrix inversion which has complexity of :math:`O(2^N)` for :math:`N`-qubit mitigation of :class:`~qiskit.result.QuasiDistribution` and expectation values. diff --git a/releasenotes/notes/0.20/fix-algorithms-7f1b969e5b2447f9.yaml b/releasenotes/notes/0.20/fix-algorithms-7f1b969e5b2447f9.yaml index 06cd8bc15e07..3178faff0e7c 100644 --- a/releasenotes/notes/0.20/fix-algorithms-7f1b969e5b2447f9.yaml +++ b/releasenotes/notes/0.20/fix-algorithms-7f1b969e5b2447f9.yaml @@ -13,7 +13,7 @@ upgrade: fixes: - | The :class:`~.AmplitudeAmplifier` is now correctly available from the root - ``qiskit.algorithms`` module directly. Previously it was not included + :mod:`qiskit.algorithms` module directly. Previously it was not included in the re-exported classes off the root module and was only accessible from ``qiskit.algorithms.amplitude_amplifiers``. Fixed `#7751 `__. diff --git a/releasenotes/notes/0.22/adapt-vqe-0f71234cb6ec92f8.yaml b/releasenotes/notes/0.22/adapt-vqe-0f71234cb6ec92f8.yaml index 8d36f57398c9..4d685f006b33 100644 --- a/releasenotes/notes/0.22/adapt-vqe-0f71234cb6ec92f8.yaml +++ b/releasenotes/notes/0.22/adapt-vqe-0f71234cb6ec92f8.yaml @@ -1,7 +1,7 @@ --- features: - | - Added a new algorithm class, :class:`~.AdaptVQE` to ``qiskit.algorithms`` + Added a new algorithm class, :class:`~.AdaptVQE` to :mod:`qiskit.algorithms` This algorithm uses a :class:`qiskit.algorithms.minimum_eigensolvers.VQE` in combination with a pool of operators from which to build out an :class:`qiskit.circuit.library.EvolvedOperatorAnsatz` adaptively. diff --git a/releasenotes/notes/0.22/prepare-0.22-118e15de86d36072.yaml b/releasenotes/notes/0.22/prepare-0.22-118e15de86d36072.yaml index 8ca521853987..deec31b207fe 100644 --- a/releasenotes/notes/0.22/prepare-0.22-118e15de86d36072.yaml +++ b/releasenotes/notes/0.22/prepare-0.22-118e15de86d36072.yaml @@ -16,7 +16,7 @@ prelude: | Additionally, :class:`~.BackendV2` backends can now optionally set custom default plugins to use for the scheduling and translation stages. - * Updated algorithm implementations in ``qiskit.algorithms`` that leverage + * Updated algorithm implementations in :mod:`qiskit.algorithms` that leverage the :mod:`~.primitives` classes that implement the :class:`~.BaseSampler` and :class:`~.BaseEstimator`. diff --git a/releasenotes/notes/0.22/project-dynamics-primitives-6003336d0866ca19.yaml b/releasenotes/notes/0.22/project-dynamics-primitives-6003336d0866ca19.yaml index 8d0fe0780c4b..6ead6d45db4f 100644 --- a/releasenotes/notes/0.22/project-dynamics-primitives-6003336d0866ca19.yaml +++ b/releasenotes/notes/0.22/project-dynamics-primitives-6003336d0866ca19.yaml @@ -1,6 +1,6 @@ features: - | - Added the :class:`~.PVQD` class to the time evolution framework in ``qiskit.algorithms``. + Added the :class:`~.PVQD` class to the time evolution framework in :mod:`qiskit.algorithms`. This class implements the projected Variational Quantum Dynamics (p-VQD) algorithm `Barison et al. `_. diff --git a/releasenotes/notes/0.24/remove-deprecated-factorizers-linear-solvers-4631870129749624.yaml b/releasenotes/notes/0.24/remove-deprecated-factorizers-linear-solvers-4631870129749624.yaml index 9f4447a412a3..330a99ef9f22 100644 --- a/releasenotes/notes/0.24/remove-deprecated-factorizers-linear-solvers-4631870129749624.yaml +++ b/releasenotes/notes/0.24/remove-deprecated-factorizers-linear-solvers-4631870129749624.yaml @@ -3,7 +3,7 @@ upgrade: - | The deprecated modules ``factorizers`` and ``linear_solvers``, containing ``HHL`` and ``Shor`` have been removed from - ``qiskit.algorithms``. These functionalities + :mod:`qiskit.algorithms`. These functionalities were originally deprecated as part of the 0.22.0 release (released on October 13, 2022). You can access the code through the Qiskit Textbook instead: `Linear Solvers (HHL) `_ , diff --git a/releasenotes/notes/0.25/deprecate-algorithms-7149dee2da586549.yaml b/releasenotes/notes/0.25/deprecate-algorithms-7149dee2da586549.yaml index 7b23c66a943b..4edaefe8bb7c 100644 --- a/releasenotes/notes/0.25/deprecate-algorithms-7149dee2da586549.yaml +++ b/releasenotes/notes/0.25/deprecate-algorithms-7149dee2da586549.yaml @@ -1,23 +1,23 @@ --- deprecations: - | - The ``qiskit.algorithms`` module has been deprecated and will be removed + The :mod:`qiskit.algorithms` module has been deprecated and will be removed in a future release. It has been superseded by a new standalone library ``qiskit-algorithms`` which can be found on PyPi or on Github here: https://github.com/qiskit-community/qiskit-algorithms - The ``qiskit.algorithms`` module will continue to work as before and bug fixes + The :mod:`qiskit.algorithms` will continue to work as before and bug fixes will be made to module until its future removal, but active development of new features has moved to the new package. - If you're relying on ``qiskit.algorithms`` you should update your + If you're relying on :mod:`qiskit.algorithms` you should update your requirements to also include ``qiskit-algorithms`` and update the imports from ``qiskit.algorithms`` to ``qiskit_algorithms``. Please note that this new package does not include already deprecated algorithms code, including ``opflow`` and ``QuantumInstance``-based algorithms. If you have not yet migrated from ``QuantumInstance``-based to primitives-based algorithms, you should follow the migration guidelines in https://qisk.it/algo_migration. - The decision to migrate the ``qiskit.algorithms`` module to a + The decision to migrate the :mod:`~.algorithms` module to a separate package was made to clarify the purpose Qiskit and make a distinction between the tools and libraries built on top of it. From 0f0f51231044b8829a12ba5d9ec9f1e1ba0c6169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:53:50 +0100 Subject: [PATCH 9/9] Update releasenotes/notes/remove-qiskit-algorithms-a43541fe24b72208.yaml --- .../notes/remove-qiskit-algorithms-a43541fe24b72208.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/remove-qiskit-algorithms-a43541fe24b72208.yaml b/releasenotes/notes/remove-qiskit-algorithms-a43541fe24b72208.yaml index 700492a75090..51112c0d064a 100644 --- a/releasenotes/notes/remove-qiskit-algorithms-a43541fe24b72208.yaml +++ b/releasenotes/notes/remove-qiskit-algorithms-a43541fe24b72208.yaml @@ -2,7 +2,7 @@ upgrade: - | The ``qiskit.algorithms`` module has been removed, following its deprecation in - Qiskit 0.44. The code has been migrated to a standalone library (``qiskit_algorithms``) + Qiskit 0.44. The primitive-based algorithms from this module have been migrated to a standalone library (``qiskit_algorithms``) and can be found on PyPi or `GitHub `_. The decision to migrate the algorithms module to a separate package was made to clarify the purpose Qiskit and make a distinction between the tools