# The IBM Quantum Account

In Qiskit we have an interface for backends and jobs that is useful for running circuits and extending to third-party backends. In this tutorial, we will review the core components of Qiskit’s base backend framework, using the IBM Quantum account as an example.

The interface has three main components: the account, backends, and jobs:

- **account**: Provides access to quantum devices and simulators, collectively called ‘backends’, and additional services tailored to a specific backend instance.

- **backend**: A quantum device or simulator capable of running quantum circuits or pulse schedules.

- **job**: A local reference to a collection of quantum circuits or pulse schedules submitted to a given backend.

Note: To run the sample code in this tutorial, you'll need to have an IBM Quantum account and have qiskit-ibm-provider installed. qiskit-ibm-provider is the interface for accessing IBM Quantum services.

## Table of contents

1) [The Account](#account)

2) [Backends](#backends)

3) [Jobs](#jobs)

## The Account

The `IBMProvider` class enables you to access your IBM Quantum account, all of the backends (quantum systems or simulators), and additional services that are available to you.

This class has class methods to save your account to disk, view saved account on disk,
delete saved account from disk and also view the account currently active in the session.
Using the constructor you can load an account from disk or enable account for current session (without saving to disk).
`IBMProvider` class can be initialized using the `API token`, which is used to authenticate to your IBM Quantum account.

Class Methods:
- `IBMProvider.save_account(TOKEN, INSTANCE)`: Save your account to disk for future use and optionally set a default hub/group/project to be used when loading your account.
- `IBMProvider.saved_accounts()`: View the accounts saved to disk.
- `IBMProvider.delete_account()`: Delete the saved account from disk.
- `IBMProvider.active_account()`: List the account currently active in the session.

Constructor Usage:
- `IBMProvider()`: Load account and default hub/group/project using saved credentials.
- `IBMProvider(TOKEN)`: Enable your account in the current session.

You can save an account to disk as shown below:

```python
from qiskit_ibm_provider import IBMProvider

# IBMProvider.save_account(TOKEN)
```

`TOKEN` here is the API token you obtain from your IBM Quantum account.

Then you can instantiate the `IBMProvider` class like below:

```python
provider = IBMProvider()
provider
```

    <IBMProvider>

The `IBMProvider` class inherits from `qiskit.providers.BaseProvider` and implements the following instance methods:

- `backends()`: Returns all backends available to the account.
- `get_backend(NAME)`: Returns the named backend.

Using the provider instance from above:

```python
provider = IBMProvider()
provider.backends()
```

    [<IBMBackend('ibmq_qasm_simulator')>,
     <IBMBackend('ibmq_armonk')>,
     <IBMBackend('ibmq_santiago')>,
     <IBMBackend('ibmq_bogota')>,
     <IBMBackend('ibmq_lima')>,
     <IBMBackend('ibmq_belem')>,
     <IBMBackend('ibmq_quito')>,
     <IBMBackend('simulator_statevector')>,
     <IBMBackend('simulator_mps')>,
     <IBMBackend('simulator_extended_stabilizer')>,
     <IBMBackend('simulator_stabilizer')>,
     <IBMBackend('ibmq_manila')>]

Selecting a backend is done by name using the `get_backend(NAME)` method:

```python
backend = provider.get_backend("ibmq_qasm_simulator")
backend
```

    <IBMBackend('ibmq_qasm_simulator')>

### Filtering the Backends

You may also optionally filter the set of returned backends, by passing arguments that query the backend’s `configuration`, `status`, or `properties`. The filters are passed by conditions and, for more general filters, you can make advanced functions using a lambda function.

As a first example lets return only those backends that are real quantum devices, and that are currently operational:

```python
provider.backends(simulator=False, operational=True)
```

    [<IBMBackend('ibmq_armonk')>,
     <IBMBackend('ibmq_santiago')>,
     <IBMBackend('ibmq_bogota')>,
     <IBMBackend('ibmq_lima')>,
     <IBMBackend('ibmq_belem')>,
     <IBMBackend('ibmq_quito')>,
     <IBMBackend('ibmq_manila')>]

You can also filter by the minimum number of qubits the backends must have:

```python
provider.backends(min_num_qubits=5, simulator=False, operational=True)
```

    [<IBMBackend('ibmq_santiago')>,
     <IBMBackend('ibmq_bogota')>,
     <IBMBackend('ibmq_lima')>,
     <IBMBackend('ibmq_belem')>,
     <IBMBackend('ibmq_quito')>,
     <IBMBackend('ibmq_manila')>]

Lastly, show the least busy 5 qubit device (in terms of the number of jobs pending in the queue)

```python
from qiskit_ibm_provider import least_busy

small_devices = provider.backends(min_num_qubits=5, simulator=False, operational=True)
backend = least_busy(small_devices)
print(backend)
```

    ibmq_belem

The above filters can be combined as desired.

## Backends

Backends represent either a simulator or a real quantum computer, and are responsible for running quantum circuits and/or pulse schedules and returning results. They have a `run` method which takes in one or more circuits as input, the Qiskit API serialization format, and returns a `BaseJob` object. This object allows asynchronous running of jobs for retrieving results from a backend when the job is completed.

At a minimum, backends use the following methods, inherited from `qiskit.providers.backend.BackendV1`:

- `provider`: Returns the instance of `IBMProvider` class used to access the backend.
- `name`: Returns the name of the backend.
- `status()`: Returns the current status of the backend.
- `configuration()`: Returns the backend configuration.
- `properties()`: Returns the backend properties.
- `run(circuit, **kwargs)`: Runs a circuit on the backend.

[`IBMBackend`](https://qiskit.org/documentation/stubs/qiskit_ibm_provider.IBMBackend.html#qiskit_ibm_provider.IBMBackend) also accepts additional parameters to the `run()` method:

- `job_name`: Custom name to be assigned to the job.
- `job_tags`: Tags to be assigned to the job.

And supports additional methods:

- `defaults()`: Gives a data structure of typical default parameters, if applicable.
- `job_limit()`: Returns the job limit for the backend.
- `remaining_jobs_count()`: Returns the number of remaining jobs that could be submitted to the backend.
- `active_jobs()`: Returns a list of unfinished jobs.

Refer to the [`IBMBackend`](https://qiskit.org/documentation/stubs/qiskit_ibm_provider.IBMBackend.html#qiskit_ibm_provider.IBMBackend) documentation for a complete list of methods.

Lets load up the least busy backend from the `small_devices` filtered above:

```python
backend = least_busy(small_devices)
```

Some examples using the different methods:

```python
backend.provider
```

    <IBMProvider>

```python
backend.name
```

    'ibmq_belem'

```python
backend.status().to_dict()
```

    {'backend_name': 'ibmq_belem',
     'backend_version': '1.0.14',
     'operational': True,
     'pending_jobs': 3,
     'status_msg': 'active'}

Here we see the name of the backend, the software version it is running, along with its operational status, number of jobs pending in the backends queue, and a more detailed status message.

Next we look at the backend configuration and properties:

```python
backend.configuration().to_dict()
```

    {'backend_name': 'ibmq_belem',
     'backend_version': '1.0.14',
     'n_qubits': 5,
     'basis_gates': ['id', 'rz', 'sx', 'x', 'cx', 'reset'],
     'gates': [{'name': 'id',
       'parameters': [],
       'qasm_def': 'gate id q { U(0, 0, 0) q; }',
       'coupling_map': [[0], [1], [2], [3], [4]]},
      {'name': 'rz',
       'parameters': ['theta'],
       'qasm_def': 'gate rz(theta) q { U(0, 0, theta) q; }',
       'coupling_map': [[0], [1], [2], [3], [4]]},
      {'name': 'sx',
       'parameters': [],
       'qasm_def': 'gate sx q { U(pi/2, 3*pi/2, pi/2) q; }',
       'coupling_map': [[0], [1], [2], [3], [4]]},
      {'name': 'x',
       'parameters': [],
       'qasm_def': 'gate x q { U(pi, 0, pi) q; }',
       'coupling_map': [[0], [1], [2], [3], [4]]},
      {'name': 'cx',
       'parameters': [],
       'qasm_def': 'gate cx q0, q1 { CX q0, q1; }',
       'coupling_map': [[0, 1],
        [1, 0],
        [1, 2],
        [1, 3],
        [2, 1],
        [3, 1],
        [3, 4],
        [4, 3]]},
      {'name': 'reset', 'parameters': None, 'qasm_def': None}],
     'local': False,
     'simulator': False,
     'conditional': False,
     'open_pulse': False,
     'memory': True,
     'max_shots': 8192,
     'coupling_map': [[0, 1],
      [1, 0],
      [1, 2],
      [1, 3],
      [2, 1],
      [3, 1],
      [3, 4],
      [4, 3]],
     'dynamic_reprate_enabled': True,
     'supported_instructions': ['id',
      'sx',
      'shiftf',
      'reset',
      'cx',
      'u1',
      'u2',
      'u3',
      'play',
      'delay',
      'x',
      'setf',
      'acquire',
      'measure',
      'rz'],
     'rep_delay_range': [0.0, 500.0],
     'default_rep_delay': 250.0,
     'max_experiments': 75,
     'sample_name': 'family: Falcon, revision: 4, segment: T',
     'n_registers': 1,
     'credits_required': True,
     'online_date': datetime.datetime(2021, 1, 8, 5, 0, tzinfo=tzutc()),
     'description': '5 qubit device Belem',
     'dt': 0.2222222222222222,
     'dtm': 0.2222222222222222,
     'processor_type': {'family': 'Falcon', 'revision': 4, 'segment': 'T'},
     'acquisition_latency': [],
     'allow_q_object': True,
     'channels': {'acquire0': {'operates': {'qubits': [0]},
       'purpose': 'acquire',
       'type': 'acquire'},
      'acquire1': {'operates': {'qubits': [1]},
       'purpose': 'acquire',
       'type': 'acquire'},
      'acquire2': {'operates': {'qubits': [2]},
       'purpose': 'acquire',
       'type': 'acquire'},
      'acquire3': {'operates': {'qubits': [3]},
       'purpose': 'acquire',
       'type': 'acquire'},
      'acquire4': {'operates': {'qubits': [4]},
       'purpose': 'acquire',
       'type': 'acquire'},
      'd0': {'operates': {'qubits': [0]}, 'purpose': 'drive', 'type': 'drive'},
      'd1': {'operates': {'qubits': [1]}, 'purpose': 'drive', 'type': 'drive'},
      'd2': {'operates': {'qubits': [2]}, 'purpose': 'drive', 'type': 'drive'},
      'd3': {'operates': {'qubits': [3]}, 'purpose': 'drive', 'type': 'drive'},
      'd4': {'operates': {'qubits': [4]}, 'purpose': 'drive', 'type': 'drive'},
      'm0': {'operates': {'qubits': [0]}, 'purpose': 'measure', 'type': 'measure'},
      'm1': {'operates': {'qubits': [1]}, 'purpose': 'measure', 'type': 'measure'},
      'm2': {'operates': {'qubits': [2]}, 'purpose': 'measure', 'type': 'measure'},
      'm3': {'operates': {'qubits': [3]}, 'purpose': 'measure', 'type': 'measure'},
      'm4': {'operates': {'qubits': [4]}, 'purpose': 'measure', 'type': 'measure'},
      'u0': {'operates': {'qubits': [0, 1]},
       'purpose': 'cross-resonance',
       'type': 'control'},
      'u1': {'operates': {'qubits': [1, 0]},
       'purpose': 'cross-resonance',
       'type': 'control'},
      'u2': {'operates': {'qubits': [1, 2]},
       'purpose': 'cross-resonance',
       'type': 'control'},
      'u3': {'operates': {'qubits': [1, 3]},
       'purpose': 'cross-resonance',
       'type': 'control'},
      'u4': {'operates': {'qubits': [2, 1]},
       'purpose': 'cross-resonance',
       'type': 'control'},
      'u5': {'operates': {'qubits': [3, 1]},
       'purpose': 'cross-resonance',
       'type': 'control'},
      'u6': {'operates': {'qubits': [3, 4]},
       'purpose': 'cross-resonance',
       'type': 'control'},
      'u7': {'operates': {'qubits': [4, 3]},
       'purpose': 'cross-resonance',
       'type': 'control'}},
     'conditional_latency': [],
     'discriminators': ['linear_discriminator',
      'quadratic_discriminator',
      'hw_centroid'],
     'hamiltonian': {'description': 'Qubits are modeled as Duffing oscillators. In this case, the system includes higher energy states, i.e. not just |0> and |1>. The Pauli operators are generalized via the following set of transformations:\n\n$(\\mathbb{I}-\\sigma_{i}^z)/2 \\rightarrow O_i \\equiv b^\\dagger_{i} b_{i}$,\n\n$\\sigma_{+} \\rightarrow b^\\dagger$,\n\n$\\sigma_{-} \\rightarrow b$,\n\n$\\sigma_{i}^X \\rightarrow b^\\dagger_{i} + b_{i}$.\n\nQubits are coupled through resonator buses. The provided Hamiltonian has been projected into the zero excitation subspace of the resonator buses leading to an effective qubit-qubit flip-flop interaction. The qubit resonance frequencies in the Hamiltonian are the cavity dressed frequencies and not exactly what is returned by the backend defaults, which also includes the dressing due to the qubit-qubit interactions.\n\nQuantities are returned in angular frequencies, with units 2*pi*GHz.\n\nWARNING: Currently not all system Hamiltonian information is available to the public, missing values have been replaced with 0.\n',
      'h_latex': '\\begin{align} \\mathcal{H}/\\hbar = & \\sum_{i=0}^{4}\\left(\\frac{\\omega_{q,i}}{2}(\\mathbb{I}-\\sigma_i^{z})+\\frac{\\Delta_{i}}{2}(O_i^2-O_i)+\\Omega_{d,i}D_i(t)\\sigma_i^{X}\\right) \\\\ & + J_{0,1}(\\sigma_{0}^{+}\\sigma_{1}^{-}+\\sigma_{0}^{-}\\sigma_{1}^{+}) + J_{1,2}(\\sigma_{1}^{+}\\sigma_{2}^{-}+\\sigma_{1}^{-}\\sigma_{2}^{+}) + J_{1,3}(\\sigma_{1}^{+}\\sigma_{3}^{-}+\\sigma_{1}^{-}\\sigma_{3}^{+}) + J_{3,4}(\\sigma_{3}^{+}\\sigma_{4}^{-}+\\sigma_{3}^{-}\\sigma_{4}^{+}) \\\\ & + \\Omega_{d,0}(U_{0}^{(0,1)}(t))\\sigma_{0}^{X} + \\Omega_{d,1}(U_{1}^{(1,0)}(t)+U_{3}^{(1,3)}(t)+U_{2}^{(1,2)}(t))\\sigma_{1}^{X} \\\\ & + \\Omega_{d,2}(U_{4}^{(2,1)}(t))\\sigma_{2}^{X} + \\Omega_{d,3}(U_{6}^{(3,4)}(t)+U_{5}^{(3,1)}(t))\\sigma_{3}^{X} \\\\ & + \\Omega_{d,4}(U_{7}^{(4,3)}(t))\\sigma_{4}^{X} \\\\ \\end{align}',
      'h_str': ['_SUM[i,0,4,wq{i}/2*(I{i}-Z{i})]',
       '_SUM[i,0,4,delta{i}/2*O{i}*O{i}]',
       '_SUM[i,0,4,-delta{i}/2*O{i}]',
       '_SUM[i,0,4,omegad{i}*X{i}||D{i}]',
       'jq0q1*Sp0*Sm1',
       'jq0q1*Sm0*Sp1',
       'jq1q2*Sp1*Sm2',
       'jq1q2*Sm1*Sp2',
       'jq1q3*Sp1*Sm3',
       'jq1q3*Sm1*Sp3',
       'jq3q4*Sp3*Sm4',
       'jq3q4*Sm3*Sp4',
       'omegad1*X0||U0',
       'omegad0*X1||U1',
       'omegad3*X1||U3',
       'omegad2*X1||U2',
       'omegad1*X2||U4',
       'omegad4*X3||U6',
       'omegad1*X3||U5',
       'omegad3*X4||U7'],
      'osc': {},
      'qub': {'0': 3, '1': 3, '2': 3, '3': 3, '4': 3},
      'vars': {'delta0': -2.1119231275656283,
       'delta1': -1.989081364755034,
       'delta2': -2.0773937776320905,
       'delta3': -2.096945401946966,
       'delta4': -2.0819029355928373,
       'jq0q1': 0.011772262300160973,
       'jq1q2': 0.012605700949390706,
       'jq1q3': 0.012591488659137172,
       'jq3q4': 0.01051661912410011,
       'omegad0': 0.8027253632102426,
       'omegad1': 0.7994609296532756,
       'omegad2': 1.9688450308371457,
       'omegad3': 0.7721838249505281,
       'omegad4': 0.6185844031359571,
       'wq0': 31.98159911982912,
       'wq1': 32.95737791154753,
       'wq2': 33.68606633828003,
       'wq3': 32.48696041179116,
       'wq4': 33.039435267685114}},
     'meas_kernels': ['hw_boxcar'],
     'meas_levels': [1, 2],
     'meas_lo_range': [[6.801661824e+18, 7.801661824e+18],
      [6.893594461e+18, 7.893594461e+18],
      [6.860214726e+18, 7.860214726e+18],
      [6.803382327e+18, 7.803382327e+18],
      [6.926310916e+18, 7.926310916e+18]],
     'meas_map': [[0, 1, 2, 3, 4]],
     'measure_esp_enabled': False,
     'multi_meas_enabled': True,
     'n_uchannels': 8,
     'parametric_pulses': ['gaussian', 'gaussian_square', 'drag', 'constant'],
     'quantum_volume': 16,
     'qubit_channel_mapping': [['d0', 'm0', 'u1', 'u0'],
      ['u4', 'd1', 'u2', 'u1', 'u3', 'm1', 'u5', 'u0'],
      ['u2', 'd2', 'm2', 'u4'],
      ['m3', 'u3', 'u7', 'd3', 'u5', 'u6'],
      ['m4', 'd4', 'u6', 'u7']],
     'qubit_lo_range': [[4.590029587904213e+18, 5.590029587904213e+18],
      [4.745329605970436e+18, 5.745329605970436e+18],
      [4.861303971058769e+18, 5.861303971058769e+18],
      [4.670460335567279e+18, 5.670460335567279e+18],
      [4.758389439816784e+18, 5.758389439816784e+18]],
     'rep_times': [0.001],
     'u_channel_lo': [[{'q': 1, 'scale': (1+0j)}],
      [{'q': 0, 'scale': (1+0j)}],
      [{'q': 2, 'scale': (1+0j)}],
      [{'q': 3, 'scale': (1+0j)}],
      [{'q': 1, 'scale': (1+0j)}],
      [{'q': 1, 'scale': (1+0j)}],
      [{'q': 4, 'scale': (1+0j)}],
      [{'q': 3, 'scale': (1+0j)}]],
     'uchannels_enabled': True,
     'url': 'None',
     'input_allowed': ['job'],
     'allow_object_storage': True,
     'pulse_num_channels': 9,
     'pulse_num_qubits': 3}

The backend configuration provides some useful information via its attributes, such as `basis_gates`, `coupling_map`, `max_experiments`, `max_shots`, `quantum_volume`, and `simulator`.

The backend properties contain data that was measured and reported. Let’s see what kind of information is reported for qubit 0.

**Note:** The following example displays the properties of the `SX` gate. Each backend may have different basis gates which may not contain `SX`.

```python
props = backend.properties()

def describe_qubit(qubit, properties):
    """Print a string describing some of reported properties of the given qubit."""

    # Conversion factors from standard SI units
    us = 1e6
    ns = 1e9
    GHz = 1e-9

    print(
        f"Qubit {qubit} has a \n"
        f"  - T1 time of {properties.t1(qubit)} microseconds\n"
        f"  - T2 time of {properties.t2(qubit)} microseconds\n"
        f"  - SX gate error of {properties.gate_error('sx', qubit)}\n"
        f"  - SX gate duration of {properties.gate_length('sx', qubit)*ns} nanoseconds\n"
        f"  - resonant frequency of {properties.frequency(qubit) * GHz} GHz"
    )

describe_qubit(0, props)
```

    Qubit 0 has a
      - T1 time of 0.000118798578854076 microseconds
      - T2 time of 8.30176098567665e-05 microseconds
      - SX gate error of 0.0003355394204181594
      - SX gate duration of 35.55555555555556 nanoseconds
      - resonant frequency of 4.820326782885405 GHz

### Backend Service

[`IBMBackendService`](https://qiskit.org/documentation/stubs/qiskit_ibm_provider.IBMBackendService.html#qiskit_ibm_provider.IBMBackendService) provides generic backend related services for an account without requiring a particular backend as input. The main methods it supports are:

- `jobs()`: Returns a list of previously submitted jobs in the account.
- `job(JOB_ID)`:  Returns a job by its job ID.

The backend service is defined as the `backend` attribute of an `IBMProvider` instance. All of the backends available to this provider are also attributes of the backend service, allowing the backend names to be autocompleted:

```python
provider.backend.ibmq_qasm_simulator
```

    <IBMBackend('ibmq_qasm_simulator')>

To see the last five jobs submitted to the account, regardless of which backend they ran on:

```python
for ran_job in provider.backend.jobs(limit=5):
    print(str(ran_job.job_id()) + " " + str(ran_job.status()))
```

    5fdccad1c01fec001a47e018 JobStatus.QUEUED
    5fdcc5fad4445500193f0072 JobStatus.DONE
    5fdcc5f923da0f001acf6f67 JobStatus.DONE
    5fdcc5f9d4445500193f0071 JobStatus.DONE
    5fdcc41b23da0f001acf6f44 JobStatus.DONE

To retrieve a particular job:

```python
if ran_job is not None:
    job = provider.backend.job(ran_job.job_id())
```

## Jobs

Job instances can be thought of as the “ticket” for a submitted job. They find out the execution state at a given point in time (for example, if the job is queued, running, or has failed), and also allow control over the job. They have the following methods:

- `status()`: Returns the status of the job.
- `backend()`: Returns the backend the job was run on.
- `job_id()`: Gets the job_id.
- `cancel()`: Cancels the job.
- `result()`: Gets the results from the circuit run.

Some of the methods that are only available to "IBM Job"([`IBMJob`](https://qiskit.org/documentation/stubs/qiskit_ibm_provider.job.IBMJob.html#qiskit_ibm_provider.job.IBMJob)) include:

- `creation_date()`: Gives the date at which the job was created.
- `queue_info()`: Returns queue information for this job, including queue position, estimated start and end time, and dynamic priorities for the hub, group, and project.
- `error_message()`: The error message of failed jobs, if any.
- `name()`: Returns the name assigned to this job.
- `properties()`: Returns the backend properties for this job.
- `time_per_step()`: Returns the time spent for each step (job creation, validation, etc).

Refer to the [`IBMJob`](https://qiskit.org/documentation/stubs/qiskit_ibm_provider.job.IBMJob.html#qiskit_ibm_provider.job.IBMJob) documentation for a complete list of methods.

Now some examples. Let's start with submitting a job:

```python
from qiskit import *
from qiskit.compiler import transpile, assemble
```

```python
qr = QuantumRegister(3)
cr = ClassicalRegister(3)
circuit = QuantumCircuit(qr, cr)
circuit.x(qr[0])
circuit.x(qr[1])
circuit.ccx(qr[0], qr[1], qr[2])
circuit.cx(qr[0], qr[1])
circuit.measure(qr, cr)
```

    <qiskit.circuit.instructionset.InstructionSet at 0x14338efd0>

You should first map the circuit using the transpile function and then send it to the backend:

```python
mapped_circuit = transpile(circuit, backend=backend)
job = backend.run(mapped_circuit, shots=1024)
```

The `status()` method returns the job status and a message:

```python
job.status()
```

    <JobStatus.VALIDATING: 'job is being validated'>

To get a backend object from the job, use the `backend()` method:

```python
backend_temp = job.backend()
backend_temp
```

    <IBMBackend('ibmq_belem')>

To get the job_id use the `job_id()` method:

```python
job.job_id()
```

    '5fdccb1ad4445500193f00db'

To get the result from the job, use the `result()` method:

```python
result = job.result()
counts = result.get_counts()
print(counts)
```

    {'000': 24, '001': 53, '010': 28, '011': 56, '100': 26, '101': 801, '110': 13, '111': 23}

If you want to check the creation date, use `creation_date()`:

```python
job.creation_date()
```

    datetime.datetime(2020, 12, 18, 10, 30, 34, 565000, tzinfo=tzlocal())

### Long list of circuits

You can submit a list of circuits in a single job, but IBM Quantum backends limit how many. You can query this limit using `backend.configuration().max_experiments`.

`qiskit-ibmq-provider`, which is the predecessor of `qiskit-ibm-provider`, has a convenience function called Job Manager that splits circuits into multiple jobs, if the number exceeds the backend limit. In `qiskit-ibm-provider`, however, this function is now done implicitly by `backend.run()`.

If you submit a list of circuits using `backend.run()`, and the length of the list exceeds the backend limit, you will get back an [`IBMCompositeJob`](https://qiskit.org/documentation/stubs/qiskit_ibm_provider.job.IBMCompositeJob.html) instance. This `IBMCompositeJob` implicitly splits the circuits into multiple sub-jobs and manage them for you. When the sub-jobs are finished, it collects and presents the results in a unified view. The sub-jobs are submitted sequentially, and if `IBMCompositeJob` hits the job limit error* when submitting a sub-job, it will wait for a previous job to finish before trying to submit again.

`IBMCompositeJob` is a subclass of `IBMJob`, and you can use it the same way you do with a traditional job. For example, you can continue to use methods like `status()` and `result()` to get the job status and result, respectively. You can also retrieve a previously executed `IBMCompositeJob` using the `provider.backend.job()` and `provider.backend.jobs()` methods, like you would with traditional jobs.

In addition to the methods that mirror those of traditional jobs, `IBMCompositeJob` also allows you to re-run failed jobs, using the `rerun_failed()` method.  You can also use the `report()` method to check the status of each individual sub-job.

*There may be a limit on the number of active jobs you can have at a time. You can find this limit using `backend.job_limit().maximum_jobs`.

Note: IBMCompositeJob uses job tags to identify sub-jobs. It is therefore important to preserve these tags. All tags used internally by IBMCompositeJob start with "ibm_composite_job_".

Now let's see some examples. The following code submits a list of circuits to `ibmq_qasm_simulator`. To save time, we will use the `max_circuits_per_job` parameter to limit the number of circuits in each job instead of using the actual backend limit.

```python
backend = provider.get_backend("ibmq_qasm_simulator")
circs = transpile([circuit] * 50, backend=backend)

# Submit them all to the backend
job = backend.run(circs, max_circuits_per_job=10)
```

You can use the `report()` method to inquire the statuses of individual sub-job:

```python
print(job.report())
```

    Composite Job ibm_composite_job_id_be6fc3313570454ba8cd4897452c37c9_:
      Summary report:
             Total jobs: 5
        Successful jobs: 0
            Failed jobs: 0
         Cancelled jobs: 0
           Running jobs: 3
           Pending jobs: 2

      Detail report:
        Circuits 0-9:
          Job index: 0
          Job ID: 6142623e2be3693f7058489e
          Status: JobStatus.RUNNING
        Circuits 10-19:
          Job index: 1
          Job ID: 6142624010b4a282afa0f641
          Status: JobStatus.RUNNING
        Circuits 20-29:
          Job index: 2
          Job ID: 614262418eca75b3a642b8da
          Status: JobStatus.RUNNING
        Circuits 30-39:
          Job index: 3
          Status: Job not yet submitted.
        Circuits 40-49:
          Job index: 4
          Status: Job not yet submitted.

To collect the result of this `IBMCompositeJob`, you can use the `result()` method, like you would with a traditional job:

```python
result = job.result()
print("This is the histogram data for experiment 5: {}".format(result.get_counts(5)))
print("This is the histogram data for experiment 15: {}".format(result.get_counts(15)))
```

    This is the histogram data for experiment 5: {'101': 1024}
    This is the histogram data for experiment 15: {'101': 1024}

This `IBMCompositeJob` has a job ID that has a different format than traditional jobs:

```python
print(f"Composite job ID: {job.job_id()}")
```

    Composite job ID: ibm_composite_job_id_be6fc3313570454ba8cd4897452c37c9_

You can still use this ID to retrieve a previously run job:

```python
retrieved_job = provider.backend.job(job.job_id())
print(retrieved_job.report())
```

    Composite Job ibm_composite_job_id_be6fc3313570454ba8cd4897452c37c9_:
      Summary report:
             Total jobs: 5
        Successful jobs: 5
            Failed jobs: 0
         Cancelled jobs: 0
           Running jobs: 0
           Pending jobs: 0

      Detail report:
        Circuits 0-9:
          Job index: 0
          Job ID: 6142623e2be3693f7058489e
          Status: JobStatus.DONE
        Circuits 10-19:
          Job index: 1
          Job ID: 6142624010b4a282afa0f641
          Status: JobStatus.DONE
        Circuits 20-29:
          Job index: 2
          Job ID: 614262418eca75b3a642b8da
          Status: JobStatus.DONE
        Circuits 30-39:
          Job index: 3
          Job ID: 614262428eca750f4a42b8db
          Status: JobStatus.DONE
        Circuits 40-49:
          Job index: 4
          Job ID: 6142624310b4a22ba7a0f642
          Status: JobStatus.DONE

At this time, `IBMCompositeJob` is not supported on the [IBM Quantum website](https://quantum-computing.ibm.com). That is, you will see the individual sub-jobs listed on the `Jobs` page rather than the composite job. You can, however, search for all sub-jobs that belong to a composite job using its job ID, as all sub-jobs have that ID assigned to them as tags.

`IBMCompositeJob` supports all the methods supported by a traditional job. For example:

```python
print(f"Job {job.job_id()} status is {job.status()}")
print(f"Job creation date is {job.creation_date()}")
```

    Job ibm_composite_job_id_be6fc3313570454ba8cd4897452c37c9_ status is JobStatus.DONE
    Job creation date is 2021-09-15 17:14:38.789000-04:00

The `sub_jobs()` method gives back a list of all submitted sub-jobs:

```python
sub_jobs = job.sub_jobs()
print(f"Job {job.job_id()} contains sub-jobs:")
for sub_job in sub_jobs:
    print(sub_job.job_id())
```

    Job ibm_composite_job_id_be6fc3313570454ba8cd4897452c37c9_ contains sub-jobs:
    6142623e2be3693f7058489e
    6142624010b4a282afa0f641
    614262418eca75b3a642b8da
    614262428eca750f4a42b8db
    6142624310b4a22ba7a0f642

```python
from qiskit_ibm_provider.version import __version__

print("qiskit-ibm-provider version: {}".format(__version__))
```

    qiskit-ibm-provider version: 0.1.0

```python
import qiskit.tools.jupyter

%qiskit_version_table
%qiskit_copyright
```

Version Information

Qiskit Software	Version

qiskit-terra	0.18.3

qiskit-aer	0.9.0

qiskit-ignis	0.6.0

qiskit-ibmq-provider (deprecated)	0.17.0

qiskit-aqua (deprecated)	0.9.5

qiskit	0.31.0

System information

Python	3.7.11 (default, Jul 27 2021, 07:03:16)
[Clang 10.0.0 ]

OS	Darwin

CPUs	8

Memory (Gb)	32.0

Mon Sep 13 00:15:27 2021 EDT

This code is a part of Qiskit

© Copyright IBM 2017, 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.

```python

```


```python
!pip install coremltools
```

    Requirement already satisfied: coremltools in /usr/local/lib/python3.6/dist-packages (4.1)
    Requirement already satisfied: attrs in /usr/local/lib/python3.6/dist-packages (from coremltools) (19.1.0)
    Requirement already satisfied: numpy<1.20,>=1.14.5 in /usr/local/lib/python3.6/dist-packages (from coremltools) (1.16.4)
    Requirement already satisfied: sympy in /usr/local/lib/python3.6/dist-packages (from coremltools) (1.7.1)
    Requirement already satisfied: scipy in /usr/local/lib/python3.6/dist-packages (from coremltools) (1.5.4)
    Requirement already satisfied: six>=1.10.0 in /usr/lib/python3/dist-packages (from coremltools) (1.11.0)
    Requirement already satisfied: protobuf>=3.1.0 in /usr/local/lib/python3.6/dist-packages (from coremltools) (3.8.0)
    Requirement already satisfied: tqdm in /usr/local/lib/python3.6/dist-packages (from coremltools) (4.58.0)
    Requirement already satisfied: packaging in /usr/local/lib/python3.6/dist-packages (from coremltools) (20.9)
    Requirement already satisfied: attr in /usr/local/lib/python3.6/dist-packages (from coremltools) (0.3.1)
    Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.6/dist-packages (from sympy->coremltools) (1.2.1)
    Requirement already satisfied: setuptools in /usr/local/lib/python3.6/dist-packages (from protobuf>=3.1.0->coremltools) (41.0.1)
    Requirement already satisfied: pyparsing>=2.0.2 in /usr/local/lib/python3.6/dist-packages (from packaging->coremltools) (2.4.0)
    WARNING: You are using pip version 19.1.1, however version 21.0.1 is available.
    You should consider upgrading via the 'pip install --upgrade pip' command.

```python
import coremltools as ct
import tensorflow as tf

print("TF Version: ", tf.__version__)
print("Eager mode enabled: ", tf.executing_eagerly())
print("GPU available: ", tf.test.is_gpu_available())
```

    TF Version:  1.14.0
    Eager mode enabled:  False
    GPU available:  False

```python
original_predict_network = 'gs://mobile-ml-wg/arbitrary_style_transfer/original/SavedModel/predict'
original_stylize_network = 'gs://mobile-ml-wg/arbitrary_style_transfer/original/SavedModel/stylize'
original_transfer_network = 'gs://mobile-ml-wg/arbitrary_style_transfer/original/SavedModel/transfer'

small_predict_network = 'gs://mobile-ml-wg/arbitrary_style_transfer/small/SavedModel/predict'
small_stylize_network = 'gs://mobile-ml-wg/arbitrary_style_transfer/small/SavedModel/stylize'
small_transfer_network = 'gs://mobile-ml-wg/arbitrary_style_transfer/small/SavedModel/transfer'
```

```python
def print_model_details(saved_model_path):
    print('\n{} model'.format(saved_model_path))
    model = tf.compat.v2.saved_model.load(saved_model_path)
    print(model.tensorflow_version)
    for input in model.signatures['serving_default'].inputs:
        print(input)
    print(model.signatures['serving_default'].structured_outputs)
```

```python
# print_model_details(original_predict_network)
# print_model_details(original_stylize_network)
# print_model_details(original_transfer_network)

# print_model_details(small_predict_network)
# print_model_details(small_stylize_network)
# print_model_details(small_transfer_network)
```

```python
def convert_tflite_model(saved_model_path, tflite_path, supported_types=[]):
    print('\n{} model'.format(saved_model_path))
    model = tf.compat.v2.saved_model.load(saved_model_path)
    concrete_func = model.signatures[tf.saved_model.DEFAULT_SERVING_SIGNATURE_DEF_KEY]
    for input in concrete_func.inputs:
        if input.name == 'style_image:0':
            input.set_shape([1, 256, 256, 3])
        elif input.name == 'content_image:0' and len(supported_types) > 0:
            input.set_shape([1, 384, 384, 3])
        elif input.name == 'mobilenet_conv/Conv/BiasAdd:0' and len(supported_types) > 0:
            input.set_shape([1, 1, 1, 100])

    converter = tf.lite.TFLiteConverter.from_concrete_functions([concrete_func])
    converter.optimizations = [tf.lite.Optimize.DEFAULT]

    if len(supported_types) > 0:
        converter.target_spec.supported_types = supported_types

    tflite_model = converter.convert()

    with tf.io.gfile.GFile(tflite_path, 'wb') as f:
        f.write(tflite_model)

    print('Quantized model:', tflite_path,
        'Size:', len(tflite_model) / 1024, "kb")
```

```python
# convert_tflite_model(original_predict_network, 'original_predict.tflite')
# convert_tflite_model(original_stylize_network, 'original_stylize.tflite')
# convert_tflite_model(original_transfer_network, 'original_transfer.tflite')

# convert_tflite_model(original_predict_network, 'original_predict_f16.tflite', [tf.float16])
# convert_tflite_model(original_stylize_network, 'original_stylize_f16.tflite', [tf.float16])
# convert_tflite_model(original_transfer_network, 'original_transfer_f16.tflite', [tf.float16])
```

```python
def print_tflite_model_details(tflite_path):
    print('\n{} model'.format(tflite_path))
    interpreter = tf.lite.Interpreter(model_path=tflite_path)
    interpreter.allocate_tensors()

    inputs = interpreter.get_input_details()
    # print('{} inputs:'.format(len(inputs)))
    for i in range(0, len(inputs)):
        print('{} {} {}'.format(inputs[i]['name'], inputs[i]['shape'], inputs[i]['dtype']))

    outputs = interpreter.get_output_details()
    # print('{} outputs:'.format(len(outputs)))
    for i in range(0, len(outputs)):
        print('{} {} {}'.format(outputs[i]['name'], outputs[i]['shape'], outputs[i]['dtype']))
```

```python
# print_tflite_model_details('original_predict.tflite')
# print_tflite_model_details('original_stylize.tflite')
# print_tflite_model_details('original_transfer.tflite')

# print_tflite_model_details('original_predict_f16.tflite')
# print_tflite_model_details('original_stylize_f16.tflite')
# print_tflite_model_details('original_transfer_f16.tflite')
```

```python
# hub_network = 'https://tfhub.dev/google/magenta/arbitrary-image-stylization-v1-256/2'
```

```python
def convert_predict_model(network):
    saved_model_path = '{}/SavedModel/predict'.format(network)
    coreml_path = '{}_style_predict.mlmodel'.format(network)
    print(saved_model_path)

    mlmodel = ct.convert(saved_model_path,
                         source='tensorflow',
                         inputs=[ct.ImageType(bias=[0,0,0], scale=1/255.0)])
    print(mlmodel)
    mlmodel.save(coreml_path)

def convert_transfer_model(network):
    saved_model_path = '{}/SavedModel/transfer'.format(network)
    coreml_path = '{}_style_transfer.mlmodel'.format(network)
    print(saved_model_path)

    mlmodel = ct.convert(saved_model_path,
                         source='tensorflow',
                         inputs=[ct.TensorType(shape=(1, 1, 1, 100)),
                                 ct.ImageType(bias=[0,0,0], scale=1/255.0)])
    print(mlmodel)
    mlmodel.save(coreml_path)
```

```python
convert_predict_model('original')
convert_transfer_model('original')
```