Пример #1
0
    def _target_quantum_state(self) -> Union[Statevector, DensityMatrix]:
        """Return the state tomography target"""
        # Check if circuit contains measure instructions
        # If so we cannot return target state
        circuit_ops = self._circuit.count_ops()
        if "measure" in circuit_ops:
            return None

        try:
            circuit = self._permute_circuit()
            if "reset" in circuit_ops or "kraus" in circuit_ops or "superop" in circuit_ops:
                state = DensityMatrix(circuit)
            else:
                state = Statevector(circuit)
        except QiskitError:
            # Circuit couldn't be simulated
            return None

        if self._meas_qubits is None:
            return state

        non_meas_qargs = list(range(len(self._meas_qubits), self._circuit.num_qubits))
        if non_meas_qargs:
            # Trace over non-measured qubits
            state = partial_trace(state, non_meas_qargs)

        return state
Пример #2
0
    def general_test(self, pauli, num_qubits=None, seed=None):
        """General test case"""
        pauli_qubits = list(range(len(pauli)))
        if num_qubits is None:
            num_qubits = len(pauli_qubits)

        # Prepare random N-qubit product input state
        # from seed
        rng = np.random.default_rng(seed)
        params = rng.uniform(-1, 1, size=(num_qubits, 3))
        init_circ = QuantumCircuit(num_qubits)
        for i, par in enumerate(params):
            init_circ.u3(*par, i)

        # Compute the target expectation value
        rho = DensityMatrix.from_instruction(init_circ)
        op = Operator.from_label(pauli)
        target = np.trace(Operator(rho).compose(op, pauli_qubits).data)

        # Simulate expectation value
        qc = init_circ.copy()
        qc.snapshot_expectation_value('final', [(1, pauli)], pauli_qubits)
        qobj = assemble(qc)
        result = self.SIMULATOR.run(
            qobj, backend_options=self.BACKEND_OPTS).result()
        self.assertSuccess(result)
        snapshots = result.data(0).get('snapshots', {})
        self.assertIn('expectation_value', snapshots)
        self.assertIn('final', snapshots['expectation_value'])
        expval = snapshots.get('expectation_value', {})['final'][0]['value']
        self.assertAlmostEqual(expval, target)
Пример #3
0
    def _target_quantum_state(self) -> Union[Statevector, DensityMatrix]:
        """Return the state tomography target"""
        # Check if circuit contains measure instructions
        # If so we cannot return target state
        circuit_ops = self._circuit.count_ops()
        if "measure" in circuit_ops:
            return None

        perm_circ = self._permute_circuit()
        try:
            if "reset" in circuit_ops or "kraus" in circuit_ops or "superop" in circuit_ops:
                state = DensityMatrix(perm_circ)
            else:
                state = Statevector(perm_circ)
        except QiskitError:
            # Circuit couldn't be simulated
            return None

        total_qubits = self._circuit.num_qubits
        if self._meas_qubits:
            num_meas = len(self._meas_qubits)
        else:
            num_meas = total_qubits
        if num_meas == total_qubits:
            return state

        # Trace out non-measurement qubits
        tr_qargs = range(num_meas, total_qubits)
        return partial_trace(state, tr_qargs)
Пример #4
0
def set_density_matrix(self, state):
    """Set the density matrix state of the simulator.

    Args:
        state (DensityMatrix): a density matrix.

    Returns:
        QuantumCircuit: with attached instruction.

    Raises:
        ExtensionError: If the density matrix is the incorrect size for the
                        current circuit.

    .. note:

        This instruction is always defined across all qubits in a circuit.
    """
    qubits = default_qubits(self)
    if not isinstance(state, DensityMatrix):
        state = DensityMatrix(state)
    if not state.num_qubits or state.num_qubits != len(qubits):
        raise ExtensionError(
            "The size of the density matrix for the set state"
            " instruction must be equal to the number of qubits"
            f" in the circuit (state.num_qubits ({state.num_qubits})"
            f" != QuantumCircuit.num_qubits ({self.num_qubits})).")
    return self.append(SetDensityMatrix(state), qubits)
Пример #5
0
    def initialize_density_matrix(self):

        # Evaluate density matrix and list of states
        for key in self.ensemble.keys():
            theta = key[0]
            phi = key[1]
            state = np.array([
                np.cos(theta / 2),
                np.sin(theta / 2) * np.exp(phi * complex(1, 0))
            ])
            self.rho = self.rho + self.ensemble[key] * np.outer(state, state)

        # Evaluate spectrum
        self.rho: np.ndarray
        v, w = np.linalg.eig(self.rho)
        s0 = Statevector(w[:, 0])
        s1 = Statevector(w[:, 1])
        self.rho = DensityMatrix(self.rho)

        # Evaluate entropy and typical basis
        if state_fidelity(s0, self.rho) > state_fidelity(s1, self.rho):
            self.s0 = s0
            self.s1 = s1
        else:
            self.s0 = s1
            self.s1 = s0
        self.entropy = -np.real(sum([p * np.log2(p) for p in v]))
        self.m = int(np.ceil(self.entropy * self.n))
def _instruction_states(instructions: List[Instruction]) -> List[np.ndarray]:
    """Construct preparation density matrices from instructions"""
    states = []
    num_qubits = instructions[0].num_qubits
    init = DensityMatrix.from_int(0, 2**num_qubits)
    for inst in instructions:
        states.append(init.evolve(inst).data)
    return states
 def test_quantum_info_input(self):
     """Test passing quantum_info.Operator and Statevector as input."""
     mark = Statevector.from_label('001')
     diffuse = 2 * DensityMatrix.from_label('000') - Operator.from_label('III')
     grover_op = GroverOperator(oracle=mark, zero_reflection=diffuse)
     self.assertGroverOperatorIsCorrect(grover_op,
                                        oracle=np.diag((-1) ** mark.data),
                                        zero_reflection=diffuse.data)
Пример #8
0
def test_qsphere_bad_dm_input():
    """Tests the qsphere raises when passed impure dm"""

    qc = QuantumCircuit(3)
    qc.h(0)
    qc.cx(0, 1)
    qc.cx(1, 2)
    dm = DensityMatrix.from_instruction(qc)
    pdm = partial_trace(dm, [0, 1])
    with pytest.raises(KaleidoscopeError):
        assert qsphere(pdm)
Пример #9
0
def _instruction_povms(instructions: List[Instruction]) -> List[Dict[int, np.ndarray]]:
    """Construct measurement outcome POVMs from instructions"""
    basis = []
    for inst in instructions:
        inst_inv = inst.inverse()
        basis_dict = {
            i: DensityMatrix.from_int(i, 2**inst.num_qubits).evolve(inst_inv).data
            for i in range(2**inst.num_qubits)
        }
        basis.append(basis_dict)
    return basis
Пример #10
0
    def __init__(self, state):
        """Create new instruction to set the density matrix state of the simulator.

        Args:
            state (DensityMatrix): a density matrix.

        Raises:
            ExtensionError: if the input density matrix is not valid.

        .. note::

            This set instruction must always be performed on the full width of
            qubits in a circuit, otherwise an exception will be raised during
            simulation.
        """
        if not isinstance(state, DensityMatrix):
            state = DensityMatrix(state)
        if not state.num_qubits or not state.is_valid():
            raise ExtensionError("The input state is not valid")
        super().__init__('set_density_matrix', state.num_qubits, 0,
                         [state.data])
Пример #11
0
    def _run_analysis(self, experiment_data, **options):
        # Extract tomography measurement data
        outcome_data, shot_data, measurement_data, preparation_data = self._fitter_data(
            experiment_data.data())

        # Get tomography options
        measurement_basis = options.pop("measurement_basis")
        preparation_basis = options.pop("preparation_basis", None)
        rescale_positive = options.pop("rescale_positive")
        rescale_trace = options.pop("rescale_trace")
        target_state = options.pop("target")

        # Get target state from circuit metadata
        if target_state == "default":
            metadata = experiment_data.metadata
            target_state = metadata.get("target", None)

        # Get tomography fitter function
        fitter = self._get_fitter(options.pop("fitter", None))
        try:
            t_fitter_start = time.time()
            state, fitter_metadata = fitter(
                outcome_data,
                shot_data,
                measurement_data,
                preparation_data,
                measurement_basis,
                preparation_basis,
                **options,
            )
            t_fitter_stop = time.time()
            if fitter_metadata is None:
                fitter_metadata = {}
            state = Choi(state) if preparation_basis else DensityMatrix(state)
            fitter_metadata["fitter"] = fitter.__name__
            fitter_metadata["fitter_time"] = t_fitter_stop - t_fitter_start

            analysis_results = self._postprocess_fit(
                state,
                metadata=fitter_metadata,
                target_state=target_state,
                rescale_positive=rescale_positive,
                rescale_trace=rescale_trace,
                qpt=bool(preparation_basis),
            )

        except AnalysisError as ex:
            raise AnalysisError(
                f"Tomography fitter failed with error: {str(ex)}") from ex

        return analysis_results, []
Пример #12
0
def _format_povms(povms: Sequence[any]) -> Tuple[Tuple[np.ndarray, ...], ...]:
    """Format sequence of basis POVMs"""
    formatted_povms = []
    # Convert from operator/channel to POVM effects
    for povm in povms:
        if isinstance(povm, (list, tuple)):
            # POVM is already an effect
            formatted_povms.append(povm)
            continue

        # Convert POVM to operator of quantum channel
        try:
            chan = Operator(povm)
        except QiskitError:
            chan = SuperOp(povm)
        adjoint = chan.adjoint()
        dims = adjoint.input_dims()
        dim = np.prod(dims)
        effects = tuple(DensityMatrix.from_int(i, dims).evolve(adjoint) for i in range(dim))
        formatted_povms.append(effects)

    # Format POVM effects to density matrix matrices
    return tuple(tuple(DensityMatrix(effect).data for effect in povm) for povm in formatted_povms)
    def _process_result(self, x: np.array) -> Dict:
        """Transforms the optimization result to a friendly format
        Args:
            x: the optimization result vector

        Returns:
            The final GST data, as dictionary.
        """
        E, rho, G_matrices = self._split_input_vector(x)
        result = {}
        result['E'] = Operator(self._convert_from_ptm(E))
        result['rho'] = DensityMatrix(self._convert_from_ptm(rho))
        for i in range(len(self.Gs)):
            result[self.Gs[i]] = PTM(G_matrices[i])
        return result
Пример #14
0
    def _target_quantum_channel(self) -> Union[Choi, Operator]:
        """Return the process tomography target"""
        # Check if circuit contains measure instructions
        # If so we cannot return target state
        circuit_ops = self._circuit.count_ops()
        if "measure" in circuit_ops:
            return None

        try:
            circuit = self._permute_circuit()
            if "reset" in circuit_ops or "kraus" in circuit_ops or "superop" in circuit_ops:
                channel = Choi(circuit)
            else:
                channel = Operator(circuit)
        except QiskitError:
            # Circuit couldn't be simulated
            return None

        total_qubits = self._circuit.num_qubits
        num_meas = total_qubits if self._meas_qubits is None else len(self._meas_qubits)
        num_prep = total_qubits if self._prep_qubits is None else len(self._prep_qubits)

        # If all qubits are prepared or measurement we are done
        if num_meas == total_qubits and num_prep == total_qubits:
            return channel

        # Convert channel to a state to project and trace out non-tomography
        # input and output qubits
        if isinstance(channel, Operator):
            chan_state = Statevector(np.ravel(channel, order="F"))
        else:
            chan_state = DensityMatrix(channel.data)

        # Get qargs for non measured and prepared subsystems
        non_meas_qargs = list(range(num_meas, total_qubits))
        non_prep_qargs = list(range(total_qubits + num_prep, 2 * total_qubits))

        # Project non-prepared subsystems on to the zero state
        if non_prep_qargs:
            proj0 = Operator([[1, 0], [0, 0]])
            for qarg in non_prep_qargs:
                chan_state = chan_state.evolve(proj0, [qarg])

        # Trace out indices to remove
        tr_qargs = non_meas_qargs + non_prep_qargs
        chan_state = partial_trace(chan_state, tr_qargs)
        channel = Choi(chan_state.data, input_dims=[2] * num_prep, output_dims=[2] * num_meas)
        return channel
Пример #15
0
    def _state_result(
        cls,
        fit: np.ndarray,
        make_positive: bool = False,
        trace: Optional[float] = None,
        input_dims: Optional[Tuple[int, ...]] = None,
        output_dims: Optional[Tuple[int, ...]] = None,
    ) -> AnalysisResultData:
        """Convert fit data to state result data"""
        # Get eigensystem of state fit
        raw_eigvals, eigvecs = cls._state_eigensystem(fit)

        # Optionally rescale eigenvalues to be non-negative
        if make_positive and np.any(raw_eigvals < 0):
            eigvals = cls._make_positive(raw_eigvals)
            fit = eigvecs @ (eigvals * eigvecs).T.conj()
            rescaled_psd = True
        else:
            eigvals = raw_eigvals
            rescaled_psd = False

        # Optionally rescale fit trace
        fit_trace = np.sum(eigvals)
        if trace is not None and not np.isclose(
                fit_trace - trace, 0, atol=1e-12):
            scale = trace / fit_trace
            fit = fit * scale
            eigvals = eigvals * scale
        else:
            trace = fit_trace

        # Convert class of value
        if input_dims and np.prod(input_dims) > 1:
            value = Choi(fit, input_dims=input_dims, output_dims=output_dims)
        else:
            value = DensityMatrix(fit, dims=output_dims)

        # Construct state result extra metadata
        extra = {
            "trace": trace,
            "eigvals": eigvals,
            "raw_eigvals": raw_eigvals,
            "rescaled_psd": rescaled_psd,
            "eigvecs": eigvecs,
        }
        return AnalysisResultData("state", value, extra=extra)
Пример #16
0
    def _run_analysis(self, experiment_data):
        # Extract tomography measurement data
        outcome_data, shot_data, measurement_data, preparation_data = self._fitter_data(
            experiment_data.data())

        # Get tomography fitter function
        fitter = self._get_fitter(self.options.fitter)
        try:
            t_fitter_start = time.time()
            state, fitter_metadata = fitter(
                outcome_data,
                shot_data,
                measurement_data,
                preparation_data,
                self.options.measurement_basis,
                self.options.preparation_basis,
                **self.options.fitter_options,
            )
            t_fitter_stop = time.time()
            if fitter_metadata is None:
                fitter_metadata = {}
            state = Choi(
                state) if self.options.preparation_basis else DensityMatrix(
                    state)
            fitter_metadata["fitter"] = fitter.__name__
            fitter_metadata["fitter_time"] = t_fitter_stop - t_fitter_start

            analysis_results = self._postprocess_fit(
                state,
                metadata=fitter_metadata,
                target_state=self.options.target,
                rescale_positive=self.options.rescale_positive,
                rescale_trace=self.options.rescale_trace,
                qpt=bool(self.options.preparation_basis),
            )

        except AnalysisError as ex:
            raise AnalysisError(
                f"Tomography fitter failed with error: {str(ex)}") from ex

        return analysis_results, []
Пример #17
0
    def optimize_circuit_sgd(self, qc, quantum_circuit_parameter, angle_degrees, cost_function='mse', learning_rate = 2):
        '''
        This function is used for optimizing the values of angle_degrees, using Gradient Descent method.

        Parameters:
        -----------
        qc                          : Quantum Circuit object
        quantum_circuit_parameter   : parameter object
        angle_degrees               : Angle(in degrees) by which the parameterised gates will rotate
        cost_function               : The type of cost function to be used[default: mse]
        learning_rate               : The learning rate for optimization[default: 2]
        '''
        i = 0
        max_i = 500
        previous_step_size = 1
        precision = -1


        loss_function = []
        vn_entropy = []
        epochs = []
        epoch_var = 0
        while i<max_i and previous_step_size>precision: #iterating over until the error converges
            epoch_var+=1
            epochs.append(epoch_var)

            theta_radians = radians(angle_degrees) #converting the degrees to radians
            previous_angle = angle_degrees

            bell_state = execute(qc, backend = Aer.get_backend('statevector_simulator'), shots = self.shots, parameter_binds=[{quantum_circuit_parameter: theta_radians}]).result().get_statevector()
            #counts = job.result().get_counts()
            psi = Statevector(bell_state)
            counts = psi.probabilities_dict()
            print(counts)

            
            D = DensityMatrix(bell_state)
            vn_entropy_val = entropy(D, base=2)
            vn_entropy.append(vn_entropy_val)

            
            
            #print(counts)
            try:
                prob_avg_01 = counts['00']
                
            except:
                prob_avg_01 = 0
            try:
                prob_avg_10 = counts['11']
            except:
                prob_avg_10 = 0
            
            if cost_function == 'mse':
                loss_function.append(self.mse_cost_function(prob_avg_01, prob_avg_10))
                angle_degrees = angle_degrees - learning_rate*self.mse_cost_function(prob_avg_01, prob_avg_10)

            if cost_function == 'unsymmetrical':
                angle_degrees = angle_degrees - learning_rate*self.unsymmetrical_cost_function(prob_avg_01, prob_avg_10)

            previous_step_size = abs(angle_degrees - previous_angle)
            i+=1
    
        print(angle_degrees)
        
        return angle_degrees, counts, epochs, vn_entropy, loss_function
              basis_gates=noise_model.basis_gates)
counts_corrected = job.result().get_counts()

# Plot QASM simulation data
plot_histogram([counts_noisy, counts_corrected],
               title='3-Qubit Error Correction $(P_{error} = ' +
               str(error_prob) + ')$',
               legend=['w/o code', 'with code'],
               figsize=(12, 9),
               bar_labels=False)
plt.subplots_adjust(left=0.15, right=0.72, top=0.9, bottom=0.15)
plt.show()

# Initialize fidelity simulation objects
job = execute(circ + qecc.encoder_ckt, backend=svsm)
init_state = DensityMatrix(job.result().get_statevector())
job = execute(qecc.syndrome_ckt, backend=unit)
syndrome_op = Kraus(job.result().get_unitary())

# Initialize fidelity simulation parameters
p_error = [0.05 * i for i in range(11)]
f1 = []
f2 = []

# Evolve initial state
for p in p_error:

    # Build noise channel
    bit_flip_channel = Kraus([[[0, np.sqrt(p)], [np.sqrt(p), 0]],
                              [[np.sqrt(1 - p), 0], [0, np.sqrt(1 - p)]]])
    bit_flip_channel = bit_flip_channel.tensor(bit_flip_channel).tensor(
Пример #19
0
def sys_evolve_den(nsites, excitations, total_time, dt, hop, U, trotter_steps):
    #Check for correct data types of input
    if not isinstance(nsites, int):
        raise TypeError("Number of sites should be int")
    if np.isscalar(excitations):
        raise TypeError("Initial state should be list or numpy array")
    if not np.isscalar(total_time):
        raise TypeError("Evolution time should be scalar")
    if not np.isscalar(dt):
        raise TypeError("Time step should be scalar")
    if not isinstance(trotter_steps, int):
        raise TypeError("Number of trotter slices should be int")

    numq = 2 * nsites
    num_steps = int(total_time / dt)
    print('Num Steps: ', num_steps)
    print('Total Time: ', total_time)
    data = []

    for t_step in range(0, num_steps):
        #Create circuit with t_step number of steps
        q = QuantumRegister(numq)
        c = ClassicalRegister(numq)
        qcirc = QuantumCircuit(q, c)

        #=========USE THIS REGION TO SET YOUR INITIAL STATE==============
        #Loop over each excitation
        for flip in excitations:
            qcirc.x(flip)
            #qcirc.h(flip)
#            qcirc.z(flip)
#===============================================================

        qcirc.barrier()
        #Append circuit with Trotter steps needed
        qc_evolve(qcirc, nsites, t_step * dt, dt, hop, U, trotter_steps)
        den_mtrx_obj = DensityMatrix.from_instruction(qcirc)
        den_mtrx = den_mtrx_obj.to_operator().data
        state_vector = qi.Statevector.from_instruction(qcirc)
        #data.append(state_vector.data)
        data.append(den_mtrx)

        #Measure the circuit
        for i in range(numq):
            qcirc.measure(i, i)
        '''
    #Choose provider and backend
        provider = IBMQ.get_provider()
        #backend = Aer.get_backend('statevector_simulator')
        backend = Aer.get_backend('qasm_simulator')
        #backend = provider.get_backend('ibmq_qasm_simulator')
        #backend = provider.get_backend('ibmqx4')
        #backend = provider.get_backend('ibmqx2')
        #backend = provider.get_backend('ibmq_16_melbourne')

        shots = 8192
        max_credits = 10 #Max number of credits to spend on execution
        job_exp = execute(qcirc, backend=backend, shots=shots, max_credits=max_credits)
        job_monitor(job_exp)
        result = job_exp.result()
        counts = result.get_counts(qcirc)
        print(result.get_counts(qcirc))
        print("Job: ",t_step+1, " of ", num_steps," complete.")
        '''
    return data
Пример #20
0
    def __init__(
        self,
        name: str,
        instructions: Optional[Sequence[Instruction]] = None,
        default_states: Optional[Sequence[Union[Statevector, DensityMatrix]]] = None,
        qubit_states: Optional[Dict[int, Sequence[Union[Statevector, DensityMatrix]]]] = None,
    ):
        """Initialize a fitter preparation basis.

        Args:
            name: a name to identity the basis.
            instructions: list of 1-qubit instructions for preparing states
                          from the :math:`|0^{\\otimes n}\\rangle` state.
            default_states: Optional, default density matrices prepared by the
                            input instructions. If None these will be determined by
                            ideal simulation of the preparation instructions.
            qubit_states: Optional, a dict with physical qubit keys and a list of
                          density matrices prepared by the list of basis instructions
                          for a specific qubit. The default states will be used for any
                          qubits not specified in this dict.

        Raises:
            QiskitError: if input states or instructions are not valid, or no
                         instructions or states are provided.
        """
        if instructions is None and default_states is None and qubit_states is None:
            raise QiskitError(
                "LocalPreparationBasis must define at least one of instructions, "
                "default_states, or qubit_states."
            )
        super().__init__(name)

        # Internal variables
        self._instructions = tuple()
        self._size = None
        self._default_states = None
        self._default_dim = None
        self._qubit_states = {}
        self._qubit_dim = {}
        self._qubits = set()
        self._custom_defaults = True

        # Format instructions so compatible types can be converted to
        # Instruction instances.
        if instructions is not None:
            self._instructions = _format_instructions(instructions)
            self._size = len(instructions)
            if default_states is None:
                default_states = self._instructions
                self._custom_defaults = False

        # Construct default states
        if default_states is not None:
            self._default_states = tuple(DensityMatrix(i).data for i in default_states)
            self._default_dim = self._default_states[0].shape[0]
            if self._size is None:
                self._size = len(self._default_states)
            elif len(self._default_states) != self._size:
                raise QiskitError(
                    "Number of instructions and number of default states must be equal."
                )

        # Construct states of specific qubits if provided
        qubit_states = qubit_states or {}
        for qubit, states in qubit_states.items():
            if self._size is None:
                self._size = len(states)
            elif len(states) != self._size:
                raise QiskitError("Number of instructions and number of states must be equal.")

            qstates = tuple(DensityMatrix(i).data for i in states)
            self._qubit_states[qubit] = qstates
            self._qubit_dim[qubit] = qstates[0].shape[0]
            self._qubits.add(qubit)

        # Pseudo hash value to make basis hashable for LRU cached functions
        self._hash = hash(
            (
                type(self),
                self._name,
                self._size,
                self._default_dim,
                self._custom_defaults,
                tuple(self._qubits),
                tuple(self._qubit_dim.values()),
                (type(i) for i in self._instructions),
            )
        )
Пример #21
0
    def __init__(self,
                 initial_state: InitialState,
                 operator: Optional[BaseOperator] = None,
                 q: Optional[float] = 0.5,
                 num_ancillae: Optional[int] = 0,
                 var_form: Optional[VariationalForm] = None,
                 optimizer: Optional[Optimizer] = None,
                 initial_point: Optional[np.ndarray] = None,
                 max_evals_grouped: int = 1,
                 callback: Optional[Callable[[int, np.ndarray, float, float], None]] = None,
                 quantum_instance: Optional[Union[QuantumInstance, BaseBackend]] = None) -> None:
        """
        Constructor.

        Args:
            initial_state (InitialState): The state to be diagonalized
            operator (BaseOperator): The density matrix of the
            initial state
            q (int): Free parameter that ones to tailer the VQSD method
            num_ancillae (int): The number of ancillae qubits if the initial
            state is a mixed state
            var_form: A parameterized variational form (ansatz).
            optimizer: A classical optimizer.
            initial_point: An optional initial point (i.e. initial parameter values)
                for the optimizer. If ``None`` then VQE will look to the variational form for a
                preferred point and if not will simply compute a random one.
            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.
            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
                variational form, the evaluated global cost, local cost and
                weighted cost
            quantum_instance: Quantum Instance or Backend
        """

        validate_min('max_evals_grouped', max_evals_grouped, 1)
        validate_range('num_ancillae', num_ancillae, 0, initial_state._num_qubits - 1)
        validate_range('q', q, 0.0, 1.0)

        if var_form is None:
            # TODO after ansatz refactor num qubits can be set later so we do not have to have
            #      an operator to create a default
            if initial_state is not None:
                var_form = RY(initial_state._num_qubits -
                              num_ancillae)

        if optimizer is None:
            optimizer = SLSQP()

        if operator is None:
            initial_state_vector = initial_state.construct_circuit(mode='vector')
            mat = np.outer(initial_state_vector, np.conj(initial_state_vector))
            operator = DensityMatrix(mat)

        # TODO after ansatz refactor we may still not be able to do this
        # if num qubits is not set on var form
        if initial_point is None and var_form is not None:
            initial_point = var_form.preferred_init_points

        self._max_evals_grouped = max_evals_grouped

        super().__init__(var_form=var_form,
                         optimizer=optimizer,
                         cost_fn=self._cost_evaluation,
                         initial_point=initial_point,
                         quantum_instance=quantum_instance)

        self._callback = callback
        self._use_simulator_snapshot_mode = None
        self._ret = None
        self._eval_time = None
        self._eval_count = 0

        logger.info(self.print_settings())
        self._var_form_params = None
        if self.var_form is not None:
            self._var_form_params = ParameterVector('θ', self.var_form.num_parameters)
        self._parameterized_circuits = None

        self._initial_state = initial_state
        self._q = q
        self._num_ancillae = num_ancillae
        self._num_working_qubits = initial_state._num_qubits - num_ancillae
        self._operator = operator
        self.initial_state = initial_state

        # TODO : Verify that if the ancillae qubits form an orthonormal basis

        # Compute state purity
        if self._num_ancillae > 0:
            # pylint: disable=import-outside-toplevel
            from qiskit.quantum_info import purity, partial_trace

            rho = self._operator.data
            ancillae_idx = list(set(range(self._initial_state._num_qubits)) -
                                set(range(self._num_ancillae)))
            self._operator = partial_trace(rho, ancillae_idx)
            self._purity = purity(self._operator)
        else:
            self._purity = 1.0