1616import math
1717from copy import deepcopy
1818from typing import Optional , Dict , Union , List , cast
19+ import warnings
1920
2021import numpy as np
2122
2425from qiskit .utils import QuantumInstance , algorithm_globals
2526from qiskit .algorithms .amplitude_amplifiers .grover import Grover
2627from qiskit .circuit .library import QuadraticForm
28+ from qiskit .primitives import BaseSampler
2729from qiskit .providers import Backend
2830from qiskit .quantum_info import partial_trace
2931from .optimization_algorithm import (
3638 QuadraticProgramToQubo ,
3739 QuadraticProgramConverter ,
3840)
41+ from ..exceptions import QiskitOptimizationError
3942from ..problems import Variable
4043from ..problems .quadratic_program import QuadraticProgram
4144
@@ -54,6 +57,7 @@ def __init__(
5457 Union [QuadraticProgramConverter , List [QuadraticProgramConverter ]]
5558 ] = None ,
5659 penalty : Optional [float ] = None ,
60+ sampler : Optional [BaseSampler ] = None ,
5761 ) -> None :
5862 """
5963 Args:
@@ -66,20 +70,35 @@ def __init__(
6670 :class:`~qiskit_optimization.converters.QuadraticProgramToQubo` will be used.
6771 penalty: The penalty factor used in the default
6872 :class:`~qiskit_optimization.converters.QuadraticProgramToQubo` converter
73+ sampler: A Sampler to use for sampling the results of the circuits.
6974
7075 Raises:
76+ ValueError: If both a quantum instance and sampler are set.
7177 TypeError: When there one of converters is an invalid type.
7278 """
7379 self ._num_value_qubits = num_value_qubits
7480 self ._num_key_qubits = 0
7581 self ._n_iterations = num_iterations
76- self ._quantum_instance = None # type: Optional[QuantumInstance]
7782 self ._circuit_results = {} # type: dict
83+ self ._converters = self ._prepare_converters (converters , penalty )
7884
79- if quantum_instance is not None :
80- self . quantum_instance = quantum_instance
85+ if quantum_instance is not None and sampler is not None :
86+ raise ValueError ( "Only one of quantum_instance or sampler can be passed, not both!" )
8187
82- self ._converters = self ._prepare_converters (converters , penalty )
88+ self ._quantum_instance = None # type: Optional[QuantumInstance]
89+ if quantum_instance is not None :
90+ warnings .warn (
91+ "The quantum_instance argument has been superseded by the sampler argument. "
92+ "This argument will be deprecated in a future release and subsequently "
93+ "removed after that." ,
94+ category = PendingDeprecationWarning ,
95+ stacklevel = 2 ,
96+ )
97+ with warnings .catch_warnings ():
98+ warnings .simplefilter ("ignore" , category = PendingDeprecationWarning )
99+ self .quantum_instance = quantum_instance
100+
101+ self ._sampler = sampler
83102
84103 @property
85104 def quantum_instance (self ) -> QuantumInstance :
@@ -88,6 +107,13 @@ def quantum_instance(self) -> QuantumInstance:
88107 Returns:
89108 The quantum instance used in the algorithm.
90109 """
110+ warnings .warn (
111+ "The quantum_instance argument has been superseded by the sampler argument. "
112+ "This argument will be deprecated in a future release and subsequently "
113+ "removed after that." ,
114+ category = PendingDeprecationWarning ,
115+ stacklevel = 2 ,
116+ )
91117 return self ._quantum_instance
92118
93119 @quantum_instance .setter
@@ -97,6 +123,13 @@ def quantum_instance(self, quantum_instance: Union[Backend, QuantumInstance]) ->
97123 Args:
98124 quantum_instance: The quantum instance to be used in the algorithm.
99125 """
126+ warnings .warn (
127+ "The GroverOptimizer.quantum_instance setter is pending deprecation. "
128+ "This property will be deprecated in a future release and subsequently "
129+ "removed after that." ,
130+ category = PendingDeprecationWarning ,
131+ stacklevel = 2 ,
132+ )
100133 if isinstance (quantum_instance , Backend ):
101134 self ._quantum_instance = QuantumInstance (quantum_instance )
102135 else :
@@ -162,11 +195,16 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult:
162195 The result of the optimizer applied to the problem.
163196
164197 Raises:
198+ ValueError: If a quantum instance or a sampler has not been provided.
199+ ValueError: If both a quantum instance and sampler are set.
165200 AttributeError: If the quantum instance has not been set.
166201 QiskitOptimizationError: If the problem is incompatible with the optimizer.
167202 """
168- if self .quantum_instance is None :
169- raise AttributeError ("The quantum instance or backend has not been set." )
203+ if self ._sampler is None and self ._quantum_instance is None :
204+ raise ValueError ("A quantum instance or sampler must be provided." )
205+
206+ if self ._quantum_instance is not None and self ._sampler is not None :
207+ raise ValueError ("Only one of quantum_instance or sampler can be passed, not both!" )
170208
171209 self ._verify_compatibility (problem )
172210
@@ -199,7 +237,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult:
199237 # Initialize oracle helper object.
200238 qr_key_value = QuantumRegister (self ._num_key_qubits + self ._num_value_qubits )
201239 orig_constant = problem_ .objective .constant
202- measurement = not self .quantum_instance .is_statevector
240+ measurement = self . _quantum_instance is None or not self ._quantum_instance .is_statevector
203241 oracle , is_good_state = self ._get_oracle (qr_key_value )
204242
205243 while not optimum_found :
@@ -246,15 +284,19 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult:
246284 threshold = optimum_value
247285
248286 # trace out work qubits and store samples
249- if self ._quantum_instance .is_statevector :
250- indices = list (range (n_key , len (outcome )))
251- rho = partial_trace (self ._circuit_results , indices )
252- self ._circuit_results = cast (Dict , np .diag (rho .data ) ** 0.5 )
253- else :
287+ if self ._sampler is not None :
254288 self ._circuit_results = {
255289 i [- 1 * n_key :]: v for i , v in self ._circuit_results .items ()
256290 }
257-
291+ else :
292+ if self ._quantum_instance .is_statevector :
293+ indices = list (range (n_key , len (outcome )))
294+ rho = partial_trace (self ._circuit_results , indices )
295+ self ._circuit_results = cast (Dict , np .diag (rho .data ) ** 0.5 )
296+ else :
297+ self ._circuit_results = {
298+ i [- 1 * n_key :]: v for i , v in self ._circuit_results .items ()
299+ }
258300 raw_samples = self ._eigenvector_to_solutions (
259301 self ._circuit_results , problem_init
260302 )
@@ -312,33 +354,52 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult:
312354
313355 def _measure (self , circuit : QuantumCircuit ) -> str :
314356 """Get probabilities from the given backend, and picks a random outcome."""
315- probs = self ._get_probs (circuit )
357+ probs = self ._get_prob_dist (circuit )
316358 logger .info ("Frequencies: %s" , probs )
317359 # Pick a random outcome.
318360 return algorithm_globals .random .choice (list (probs .keys ()), 1 , p = list (probs .values ()))[0 ]
319361
320- def _get_probs (self , qc : QuantumCircuit ) -> Dict [str , float ]:
362+ def _get_prob_dist (self , qc : QuantumCircuit ) -> Dict [str , float ]:
321363 """Gets probabilities from a given backend."""
322364 # Execute job and filter results.
323- result = self .quantum_instance .execute (qc )
324- if self .quantum_instance .is_statevector :
325- state = result .get_statevector (qc )
326- if not isinstance (state , np .ndarray ):
327- state = state .data
328- keys = [
329- bin (i )[2 ::].rjust (int (np .log2 (len (state ))), "0" )[::- 1 ] for i in range (0 , len (state ))
330- ]
331- probs = [abs (a ) ** 2 for a in state ]
332- total = math .fsum (probs )
333- probs = [p / total for p in probs ]
334- hist = {key : prob for key , prob in zip (keys , probs ) if prob > 0 }
335- self ._circuit_results = state
365+ if self ._sampler is not None :
366+ job = self ._sampler .run ([qc ])
367+
368+ try :
369+ result = job .result ()
370+ except Exception as exc :
371+ raise QiskitOptimizationError ("Sampler job failed." ) from exc
372+ quasi_dist = result .quasi_dists [0 ]
373+ bit_length = (len (quasi_dist ) - 1 ).bit_length ()
374+ prob_dist = {f"{ i :0{bit_length }b} " [::- 1 ]: v for i , v in quasi_dist .items ()}
375+ self ._circuit_results = {
376+ f"{ i :0{bit_length }b} " : v ** 0.5
377+ for i , v in quasi_dist .items ()
378+ if not np .isclose (v , 0 )
379+ }
336380 else :
337- state = result .get_counts (qc )
338- shots = self .quantum_instance .run_config .shots
339- hist = {key [::- 1 ]: val / shots for key , val in sorted (state .items ()) if val > 0 }
340- self ._circuit_results = {b : (v / shots ) ** 0.5 for (b , v ) in state .items ()}
341- return hist
381+ result = self ._quantum_instance .execute (qc )
382+ if self ._quantum_instance .is_statevector :
383+ state = result .get_statevector (qc )
384+ if not isinstance (state , np .ndarray ):
385+ state = state .data
386+ keys = [
387+ bin (i )[2 ::].rjust (int (np .log2 (len (state ))), "0" )[::- 1 ]
388+ for i in range (0 , len (state ))
389+ ]
390+ probs = [abs (a ) ** 2 for a in state ]
391+ total = math .fsum (probs )
392+ probs = [p / total for p in probs ]
393+ prob_dist = {key : prob for key , prob in zip (keys , probs ) if prob > 0 }
394+ self ._circuit_results = state
395+ else :
396+ state = result .get_counts (qc )
397+ shots = self ._quantum_instance .run_config .shots
398+ prob_dist = {
399+ key [::- 1 ]: val / shots for key , val in sorted (state .items ()) if val > 0
400+ }
401+ self ._circuit_results = {b : (v / shots ) ** 0.5 for (b , v ) in state .items ()}
402+ return prob_dist
342403
343404 @staticmethod
344405 def _bin_to_int (v : str , num_value_bits : int ) -> int :
0 commit comments