Closed nulinspiratie closed 3 months ago
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)”?
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.
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 :-)
Hi @nulinspiratie, would it be possible to be assigned to this issue? I think UnitaryHACK needs it to know I've solved it. Thanks!
Also in this one, apologies but it's fixed now
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 configurationsimulate
is called/api/submit_qua_program
- Submit a QUA programsimulate
is called/api/submit_quantum_system
- Submit the simulation quantum system/api/submit_channel_map
- Submit the qua → qiskit channel map/api/simulate
- Simulate system using the latest QUA program, configuration, etc.num_shots
Default is 1000TransmonPairBackendFromQUA
/api/status
- Check simulation status/api/reset
- Forget QUA configuration, program, quantum_system, channel_map, results, and any visuals on the frontendAcceptance criteria
api/simulate
should start the simulation