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

Create frontend interface #4

Closed
nulinspiratie opened this issue May 21, 2024 · 5 comments · Fixed by #9
Closed

Create frontend interface #4

nulinspiratie opened this issue May 21, 2024 · 5 comments · Fixed by #9
Assignees
Labels

Comments

@nulinspiratie
Copy link
Contributor

nulinspiratie commented May 21, 2024

Create a web app to interact with simulation results

The current Qiskit backend can compile a QUA program into a Qiskit Pulse Schedule.
After the compilation, the Pulse Schedule can be run on a simulated quantum system, providing results in the form of arrays.
Encapsulating the compiler and simulator into a web app would allow the outputs (Pulse Schedule + simulated results) to be visualized in a webpage.
External programs such as an IDE can submit QUA programs to be simulated on the web app backend, and the results are then viewed on the web frontend, enabling seamless integration into existing workflows.

This feature can serve as a base for issues #5 and #6

Requirements

Dash and FastAPI

It is recommended that both the Pulse Schedule and Simulated results are displayed on a webpage using Plotly Dash.
However, suitable alternative solutions can also be proposed.

To combine this into a web app with an API, Dash should be embedded into FastAPI.
For details see Embedding Dash dashboards in FastPI framework
This allows external programs to submit QUA programs, configurations, and other necessary settings.

FastAPI API calls

  • /api/submit_qua_configuration - Submit QUA configuration
    • The QUA configuration is passed in the request body as json.
    • The backend should remember the latest provided QUA configuration for when simulate is called
  • /api/submit_qua_program - Submit a QUA program
    • There should be two ways in which a QUA program can be sent:
      • As a multiline string corresponding to the QUA script to create the program
      • As a binary Dill object (the qua.program object)
    • The backend should remember the latest provided QUA program for when simulate is called
  • /api/submit_quantum_system - Submit the simulation quantum system
    • The quantum system should be sent as a binary dill object
  • /api/submit_channel_map - Submit the qua → qiskit channel map
    • The channel map should be passed as a binary Dill object
  • /api/simulate - Simulate system using the latest QUA program, configuration, etc.
    • Arguments
      • num_shots Default is 1000
      • For now we fix backend to TransmonPairBackendFromQUA
  • /api/status - Check simulation status
    • The response should give an HTTP response code. In case anything in the simulation failed, it should also indicate what has gone wrong (e.g. invalid QUA configuration).
  • /api/reset - Forget QUA configuration, program, quantum_system, channel_map, results, and any visuals on the frontend

Acceptance criteria

  1. Each of the four components in the readme example can be submitted separately through the API
  2. After submitting all four components, a call to api/simulate should start the simulation
  3. Once the simulation is complete, the web frontend should display the simulated results similar to the example
@Piwakk
Copy link
Contributor

Piwakk commented May 30, 2024

Hi! I have started to work on this issue as part of unitaryHACK 2024. However, I've come across a bug in serialization / deserialization of a Program. One can reproduce the issue by inserting a serialization / deserialization step in test_simultaneous_rabi (test_rabi.py):

def test_simultaneous_rabi(transmon_pair_backend, transmon_pair_qua_config, config_to_transmon_pair_backend_map):
    start, stop, step = -2, 2, 0.1
    with program() as prog:
        a = declare(fixed)

        with for_(a, start, a < stop - 0.0001, a + step):
            play("x90"*amp(a), "qubit_1")
            play("x90"*amp(a), "qubit_2")

            align("qubit_1", "qubit_2", "resonator_1", "resonator_2")
            measure("readout", "resonator_1", None)
            measure("readout", "resonator_2", None)

    new_prog = dill.loads(dill.dumps(prog))

    results = simulate_program(
        qua_program=new_prog,
        qua_config=transmon_pair_qua_config,
        qua_config_to_backend_map=config_to_transmon_pair_backend_map,
        backend=transmon_pair_backend,
        num_shots=10_000,
        # schedules_to_plot=[0]
    )
    # plt.show()

The error:

quaqsim/simulate.py:18: in simulate_program
    sim = compiler.compile(qua_program, qua_config_to_backend_map, backend)
quaqsim/program_to_quantum_pulse_sim_compiler/quantum_pulse_sim_compiler.py:21: in compile
    program_tree = ProgramTreeBuilder().build(program)
quaqsim/program_dict_to_program_compiler/program_tree_builder.py:15: in build
    return visitor.visit(program_body)
quaqsim/program_dict_to_program_compiler/visitors/program_visitor.py:10: in visit
    body_dict = program.body._body.to_dict()
../../miniforge3/envs/qua-qsim/lib/python3.11/site-packages/betterproto/__init__.py:1112: in to_dict
    value = [
../../miniforge3/envs/qua-qsim/lib/python3.11/site-packages/betterproto/__init__.py:1113: in <listcomp>
    i.to_dict(casing, include_default_values) for i in value
../../miniforge3/envs/qua-qsim/lib/python3.11/site-packages/betterproto/__init__.py:1127: in to_dict
    output[cased_name] = value.to_dict(casing, include_default_values)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = QuaProgramPlayStatement(qe=QuaProgramQuantumElementReference(name='qubit_1', loc='File "qua-qsim/tes...object at 0x7f8c937b7730>, loc='File "qua-qsim/test/test_rabi.py", line 12: play("x90", "qubit_1") ')
casing = <function camel_case at 0x7f8cfd0958a0>, include_default_values = False

    def to_dict(
        self, casing: Casing = Casing.CAMEL, include_default_values: bool = False
    ) -> Dict[str, Any]:
        """
        Returns a JSON serializable dict representation of this object.
    
        Parameters
        -----------
        casing: :class:`Casing`
            The casing to use for key values. Default is :attr:`Casing.CAMEL` for
            compatibility purposes.
        include_default_values: :class:`bool`
            If ``True`` will include the default values of fields. Default is ``False``.
            E.g. an ``int32`` field will be included with a value of ``0`` if this is
            set to ``True``, otherwise this would be ignored.
    
        Returns
        --------
        Dict[:class:`str`, Any]
            The JSON serializable dict representation of this object.
        """
        output: Dict[str, Any] = {}
        field_types = self._type_hints()
        defaults = self._betterproto.default_gen
        for field_name, meta in self._betterproto.meta_by_field_name.items():
            field_is_repeated = defaults[field_name] is list
            value = getattr(self, field_name)
            cased_name = casing(field_name).rstrip("_")  # type: ignore
            if meta.proto_type == TYPE_MESSAGE:
                if isinstance(value, datetime):
                    if (
                        value != DATETIME_ZERO
                        or include_default_values
                        or self._include_default_value_for_oneof(
                            field_name=field_name, meta=meta
                        )
                    ):
                        output[cased_name] = _Timestamp.timestamp_to_json(value)
                elif isinstance(value, timedelta):
                    if (
                        value != timedelta(0)
                        or include_default_values
                        or self._include_default_value_for_oneof(
                            field_name=field_name, meta=meta
                        )
                    ):
                        output[cased_name] = _Duration.delta_to_json(value)
                elif meta.wraps:
                    if value is not None or include_default_values:
                        output[cased_name] = value
                elif field_is_repeated:
                    # Convert each item.
                    cls = self._betterproto.cls_by_field[field_name]
                    if cls == datetime:
                        value = [_Timestamp.timestamp_to_json(i) for i in value]
                    elif cls == timedelta:
                        value = [_Duration.delta_to_json(i) for i in value]
                    else:
                        value = [
                            i.to_dict(casing, include_default_values) for i in value
                        ]
                    if value or include_default_values:
                        output[cased_name] = value
                elif value is None:
                    if include_default_values:
                        output[cased_name] = value
                elif (
>                   value._serialized_on_wire
                    or include_default_values
                    or self._include_default_value_for_oneof(
                        field_name=field_name, meta=meta
                    )
                ):
E               AttributeError: 'object' object has no attribute '_serialized_on_wire'

../../miniforge3/envs/qua-qsim/lib/python3.11/site-packages/betterproto/__init__.py:1121: AttributeError

I've the same error when using pickle instead of dill.

Versions of seemingly relevant libraries:

betterproto           2.0.0b6
dill                  0.3.8
protobuf              4.25.3
qm-octave             2.1.0
qm-qua                1.1.7
qualang-tools         0.17.4

Has someone encountered this error before? @nulinspiratie did you have something else in mind when mentioning “a binary Dill object (the qua.program object)”?

@deanpoulos
Copy link
Collaborator

Hi @Piwakk! I see that you're trying to serialize the program itself, and although I can't comment on why it's not serializable, I know it's not the most friendly data structure and not everything that's valid in that object is supported for Qua-Qsim.

I suggest if you want to serialize the program, you should serialize the program AST:

It is the object created on line 21 here in the first pass of the compiler:

program_tree = ProgramTreeBuilder().build(program)

Although this data structure is currently hidden a couple of layers down into the simulate_program function, you can unwrap those functions and deal with the raw objects yourself.

@nulinspiratie
Copy link
Contributor Author

Hi @Piwakk great to have you on board!

I wasn't aware that serializing a QUA program would cause issues. I agree with @deanpoulos that serializing ProgramTreeBuilder is a good solution here.
Let us know if you have any other questions/comments :-)

@Piwakk
Copy link
Contributor

Piwakk commented Jun 17, 2024

Hi @nulinspiratie, would it be possible to be assigned to this issue? I think UnitaryHACK needs it to know I've solved it. Thanks!

@nulinspiratie
Copy link
Contributor Author

Also in this one, apologies but it's fixed now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants