Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Making custom backends is much more difficult than previous releases #1828

Closed
nonhermitian opened this issue Feb 19, 2019 · 12 comments
Closed
Labels
type: enhancement It's working, but needs polishing type: feature request New feature or request

Comments

@nonhermitian
Copy link
Contributor

What is the expected behavior?

In Terra-0.6 one could create a custom backend to compile against by simply doing:

class LNN4(object):
    """A fake 4 qubit linear backend.
    """
    def configuration(self):
        lnn_cmap = [[0,1], [1,0], [1,2], [2,1], [2,3], [3,2]]
        return {
            'name': 'lnn4', 'basis_gates': 'u1,u2,u3,cx,id',
            'simulator': False, 'n_qubits': 4,
            'coupling_map': lnn_cmap
        }

Now it requires the following:

from qiskit.providers import BaseBackend
from qiskit.providers.models import BackendProperties, BackendConfiguration
from qiskit.providers.models.backendconfiguration import GateConfig

class FakeBackend(BaseBackend):
    """This is a dummy backend just for testing purposes."""

    def __init__(self, configuration, time_alive=10):
        """
        Args:
            configuration (BackendConfiguration): backend configuration
            time_alive (int): time to wait before returning result
        """
        super().__init__(configuration)
        self.time_alive = time_alive

    def properties(self):
        """Return backend properties"""
        properties = {
            'backend_name': self.name(),
            'backend_version': self.configuration().backend_version,
            'last_update_date': '2000-01-01 00:00:00Z',
            'qubits': [[{'name': 'TODO', 'date': '2000-01-01 00:00:00Z',
                         'unit': 'TODO', 'value': 0}]],
            'gates': [{'qubits': [0], 'gate': 'TODO',
                       'parameters':
                           [{'name': 'TODO', 'date': '2000-01-01 00:00:00Z',
                             'unit': 'TODO', 'value': 0}]}],
            'general': []
        }

        return BackendProperties.from_dict(properties)

    def run(self, qobj):
        job_id = str(uuid.uuid4())
        job = FakeJob(self, job_id, self.run_job, qobj)
        job.submit()
        return job

    # pylint: disable=unused-argument
    def run_job(self, job_id, qobj):
        """Main dummy run loop"""
        time.sleep(self.time_alive)

        return Result.from_dict(
            {'job_id': job_id, 'result': [], 'status': 'COMPLETED'})


class LNN4(FakeBackend):
    """A fake 4 qubit linear backend."""

    def __init__(self):
        cmap = [[0,1], [1,0], [1,2], [2,1], [2,3], [3,2]]

        configuration = BackendConfiguration(
            backend_name='fake_lnn4',
            backend_version='0.0.0',
            n_qubits=4,
            basis_gates=['u1', 'u2', 'u3', 'cx', 'id'],
            simulator=False,
            local=True,
            conditional=False,
            open_pulse=False,
            memory=False,
            max_shots=65536,
            gates=[GateConfig(name='TODO', parameters=[], qasm_def='TODO')],
            coupling_map=cmap,
        )

        super().__init__(configuration)

Creating custom, abstracted backends is very convenient for creating generic instances of circuits that can later be mapped to a given actual backend via initial_layout. There should be a helper function that makes building these objects easier.

@nonhermitian nonhermitian added type: enhancement It's working, but needs polishing type: feature request New feature or request labels Feb 19, 2019
@nonhermitian nonhermitian added this to To do in Backends, Qobj, and Result via automation Feb 19, 2019
@ajavadia
Copy link
Member

The FakeBackend is already defined in qiskit.test.mock. Take a look at that file. It shows mocked versions of Tenerife, Rueschlikon, Tokyo.

So creating another one shouldn't be hard if you just derive from FakeBackend.

@nonhermitian
Copy link
Contributor Author

True, that is where I got it from, but I should not need to know about FakeBackend (that is in a odd location to begin with) as well as the need to use GateConfig or BackendConfiguation. I should be able to do something like abstract_backend(n_qubits, coupling_map) and have it do it for me.

@ajavadia
Copy link
Member

maybe @diego-plan9 can comment on whether some of those fields can be turned into a default value if not provided. or if there's a way to turn off validation so the validator does not complain.

So would write something like:

class LNN4(FakeBackend):
    """A fake 4 qubit linear backend."""

    def __init__(self):
        cmap = [[0,1], [1,0], [1,2], [2,1], [2,3], [3,2]]

        configuration = BackendConfiguration(
            backend_name='fake_lnn4',
            n_qubits=4,
            basis_gates=['u1', 'u2', 'u3', 'cx', 'id'],
            simulator=False,
            local=True,
            coupling_map=cmap,
        )

        super().__init__(configuration)

Here you would only add the BackendConfiguration part compared to 0.6.

I'm open to moving FakeBackend elsewhere and highlighting its use. But this is all part of standardizing the configuration format.

@diego-plan9
Copy link
Member

I'd rather turn the attention to:

Creating custom, abstracted backends is very convenient for creating generic instances of circuits that can later be mapped to a given actual backend via initial_layout.

Can you elaborate on the need for them, and the use case and scenarios/flow where the need arises? It might hint towards a different problem (the compilation process as a whole being too "bound" to a backend instance in some steps where just requiring the relevant pieces of data would be enough and more flexible). If that is the case and the need is common enough, I'd rather try to tackle the root cause via some reorganizing instead of introducing the concept of a fake backend as part of terra.

@nonhermitian
Copy link
Contributor Author

nonhermitian commented Feb 20, 2019

I basically solved this myself in #1837. However, many times you want to create a custom topology and compile against it. For example, a linear chain 0 - 1 - 2 - 3 is an useful backend to have. Compiling against this backend gives you generic circuits that you can then run on an actual device using the initial_layout to map. I end up doing this all the time for quantum volume circuits, and for compiling against topologies that are currently not implemented in physical devices.

@diego-plan9
Copy link
Member

I end up doing this all the time for quantum volume circuits, and for compiling against topologies that are currently not implemented in physical devices

Can you share a minimal example of the relevant parts where you use a fake backend for that purpose?

@nonhermitian
Copy link
Contributor Author

It is easy compile(qv_circuits, backend=FakeBackend). Then to run on a given device you would do compile(qv_circuits, backend=RealBackend, initial_layout=[....])

@nonhermitian
Copy link
Contributor Author

I think #1837 really solves all the issues.

@diego-plan9
Copy link
Member

After some digging, it seems that the issue boils down to the need of specifying a coupling_map for the compilation process - and actually it can already be done in master by just calling:

compile(circuits, backend=None, coupling_map=foo, basis_gates=bar)

(since it calls transpile, which only uses the backend for fetching coupling_map and basis_gates if they are not already supplied). Minor usability issues aside, if this is enough to cover the use case, I believe this should be the encouraged solution for users needing that feature rather than having to introduce a "fake backend" construct.

Depending on how needed this feature is, we could think of sweetening it a bit in terra - either via some convenience functions that are a variation of :

def generic_compile_with_a_custom_coupling_map_etc(circuits, coupling_map):
    return compile(circuits, backend=None,
        coupling_map=coupling_map, basis_gates=sensible_default)

or by revising the current compile(), transpile() parameter handling a bit. However, if it caters to a rather specific use case, it could be worth assuming it belongs to "user space" for the time being, and assume users will be comfortable implementing their convenience functions on their own.

@nonhermitian
Copy link
Contributor Author

I am not sure why there is so much hesitation to expose to users a functionality that we use ourselves, but I will close this issue, and #1837 and just add the routine to my "user space"

Backends, Qobj, and Result automation moved this from To do to Done Feb 20, 2019
@ajavadia
Copy link
Member

@jaygambetta also wants to change the compile() signature to not take a backend, it will only take the compilation options (e.g. basis_gates, coupling_map, etc.).. so then I think there will be less use for making an abstract backend. The reason they exist in tests is because they mock job responses.

@nonhermitian
Copy link
Contributor Author

No worries, I can add it to an addons package in the mean time

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement It's working, but needs polishing type: feature request New feature or request
Projects
Development

Successfully merging a pull request may close this issue.

3 participants