# Importing standard Qiskit libraries
from qiskit import QuantumCircuit, transpile
from qiskit_ibm_provider import IBMProvider
import qiskit_ibm_provider.jupyter
 
#provider = IBMProvider('ibm-q')
#backend = provider.get_backend('ibmq_vigo')
from qiskit.visualization import *
from ibm_quantum_widgets import *

# qiskit-ibmq-provider has been deprecated.
# Please see the Migration Guides in https://ibm.biz/provider_migration_guide for more detail.
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Estimator, Session, Options

# Loading your IBM Quantum account(s)
service = QiskitRuntimeService(channel="ibm_quantum")

# Invoke a primitive. For more details see https://qiskit.org/documentation/partners/qiskit_ibm_runtime/tutorials.html
# result = Sampler("ibmq_qasm_simulator").run(circuits).result()

# Built-in modules
import math

# Imports from Qiskit
from qiskit import QuantumCircuit
from qiskit.circuit.library import GroverOperator, MCMT, ZGate
from qiskit.visualization import plot_distribution

# Imports from Qiskit Runtime
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Session

from qiskit.circuit import QuantumCircuit, Gate, Instruction
from qiskit.circuit.library.standard_gates import ZGate

def MCMT(gate, num_controls, num_targets):
    """Construct a multi-controlled multi-target gate.

    Args:
        gate (Gate): The gate to apply to the target qubits.
        num_controls (int): The number of control qubits.
        num_targets (int): The number of target qubits.

    Returns:
        Instruction: The multi-controlled multi-target gate as a Qiskit Instruction.
    """
    mcmt_gate = QuantumCircuit(num_controls + num_targets)
    mcmt_gate.append(gate.control(num_controls), list(range(num_controls + num_targets)))
    return mcmt_gate.to_instruction()

def grover_oracle(marked_states):
    """Build a Grover oracle for multiple marked states

    Here we assume all input marked states have the same number of bits

    Parameters:
        marked_states (str or list): Marked states of oracle

    Returns:
        QuantumCircuit: Quantum circuit representing Grover oracle
    """
    if not isinstance(marked_states, list):
        marked_states = [marked_states]
    # Compute the number of qubits in circuit
    num_qubits = len(marked_states[0])

    qc = QuantumCircuit(num_qubits)
    # Mark each target state in the input list
    for target in marked_states:
        # Flip target bit-string to match Qiskit bit-ordering
        rev_target = target[::-1]
        # Find the indices of all the '0' elements in bit-string
        zero_inds = [ind for ind in range(num_qubits) if rev_target.startswith("0", ind)]
        # Add a multi-controlled Z-gate with pre- and post-applied X-gates (open-controls)
        # where the target bit-string has a '0' entry
        qc.x(zero_inds)
        #qc.ry(0.1,zero_inds)
        qc.compose(MCMT(ZGate(), num_qubits - 1, 1), inplace=True)
        qc.x(zero_inds)
    return qc

marked_states = ["011", "100"]

oracle = grover_oracle(marked_states)
oracle.draw("mpl")

grover_op = GroverOperator(oracle)
grover_op.decompose().draw("mpl")

optimal_num_iterations = math.floor(
    math.pi / 4 * math.sqrt(2**grover_op.num_qubits / len(marked_states))
)

qc = QuantumCircuit(grover_op.num_qubits)
# Create even superposition of all basis states
qc.h(range(grover_op.num_qubits))
# Apply Grover operator the optimal number of times
qc.compose(grover_op.power(optimal_num_iterations), inplace=True)
# Measure all qubits
qc.measure_all()
qc.draw("mpl")

from qiskit import QuantumCircuit
from qiskit.providers.basic_provider import BasicProvider
from qiskit.visualization import plot_histogram
from qiskit import transpile
from qiskit.providers.fake_provider import GenericBackendV2
#from qiskit.visualization import plot_histogram
 
# Generate a 5-qubit simulated backend
backend = GenericBackendV2(num_qubits=5)
"""
# Alternative
from qiskit_ibm_runtime.fake_provider import FakeProvider
backend1 = FakeProvider().get_backend("fake_ourense")
 
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
backend2 = FakeSherbrooke()"""
# Transpile the ideal circuit to a circuit that can be directly executed by the backend
transpiled_circuit = transpile(qc, backend)#transpiled_circuit = transpile(qc, backend=backend_simulator)
transpiled_circuit.draw('mpl')
 
# Run the transpiled circuit using the simulated backend
job = backend.run(transpiled_circuit)
counts = job.result().get_counts()
print(counts)
plot_histogram(counts)

from qiskit import QuantumCircuit
from qiskit.providers.basic_provider import BasicProvider
from qiskit.visualization import plot_histogram
from qiskit import transpile
from qiskit.providers.fake_provider import GenericBackendV2
#from qiskit.visualization import plot_histogram
 
# Generate a 5-qubit simulated backend
backend = GenericBackendV2(num_qubits=5)
"""
# Alternative
from qiskit_ibm_runtime.fake_provider import FakeProvider
backend1 = FakeProvider().get_backend("fake_ourense")
 
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
backend2 = FakeSherbrooke()"""
# Transpile the ideal circuit to a circuit that can be directly executed by the backend
transpiled_circuit = transpile(qc, backend)#transpiled_circuit = transpile(qc, backend=backend_simulator)
transpiled_circuit.draw('mpl')
 
# Run the transpiled circuit using the simulated backend
job = backend.run(transpiled_circuit)
counts = job.result().get_counts()
print(counts)
plot_histogram(counts)

from qiskit import QuantumCircuit
from qiskit.providers.basic_provider import BasicProvider
from qiskit.visualization import plot_histogram

#qc.measure_all()

from qiskit.providers.basic_provider import BasicProvider
backend = BasicProvider().get_backend("basic_simulator")
result = backend.run(qc).result()
 
"""
# Get the BasicSimulator backend from the BasicProvider
backend = BasicProvider().get_backend("basic_simulator")

# Simulate the quantum circuit
result = backend.run(qc).result()"""
# Alternative
from qiskit_ibm_runtime.fake_provider import FakeProvider
backend1 = FakeProvider().get_backend("fake_ourense")
 
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
backend2 = FakeSherbrooke()

# Get counts from the result
counts = result.get_counts()

# Plot the histogram
plot_histogram(counts)

backend_simulator = service.least_busy(operational=True, simulator=False)
backend_simulator.name

# Initialize your session
sim_session = Session(backend=backend_simulator)
sim_sampler = Sampler(session=sim_session)

from qiskit import transpile
from qiskit.visualization import plot_histogram
# Assuming qc is your quantum circuit
# Transpile the circuit to match the target backend
transpiled_circuit = transpile(qc, backend=backend_simulator)

from qiskit.providers.basic_provider import BasicProvider
backend = BasicProvider().get_backend("ibmq_qasm_simulator")

job = backend.run(transpiled_circuit)
job = execute(qc, simulator, shots=1024)

# Wait for the job to finish
job_result = job.result()

# Get counts from the result
counts = job_result.get_counts()

# Plot the histogram
plot_histogram(counts)

from qiskit import transpile
from qiskit.visualization import plot_histogram
# Assuming qc is your quantum circuit
# Transpile the circuit to match the target backend
transpiled_circuit = transpile(qc, backend=backend_simulator)

# Run the transpiled circuit on the backend
job = backend_simulator.run(transpiled_circuit)

# Wait for the job to finish
job_result = job.result()

# Get counts from the result
counts = job_result.get_counts()

# Plot the histogram
plot_histogram(counts)

sim_dist = sim_sampler.run(qc, shots=int(1e4)).result().quasi_dists[0]

plot_distribution(sim_dist.binary_probabilities())

sim_session.close()

# Select the backend with the fewest number of jobs in the queue
backend = service.least_busy(simulator=False, operational=True)
backend.name

# Initialize your session
session = Session(backend=backend)

real_sampler = Sampler(session=session)

real_dist = real_sampler.run(qc, shots=int(1e4)).result().quasi_dists[0]

plot_distribution(real_dist.binary_probabilities())

# Close session
session.close()