{ "cells": [ { "cell_type": "markdown", "id": "dbbfb5b8", "metadata": {}, "source": [ "# MerLin release 0.3 highlights\n", "\n", "We are presenting the new features and changes introduced in MerLin 0.3.x. " ] }, { "cell_type": "markdown", "id": "9e99c03f", "metadata": {}, "source": [ "## 0. Imports" ] }, { "cell_type": "code", "execution_count": null, "id": "aac03ae9", "metadata": {}, "outputs": [], "source": [ "import merlin as ml" ] }, { "cell_type": "markdown", "id": "fc7d295c", "metadata": {}, "source": [ "## 1. New features\n", "\n", "Here is a brief overview of the main new features." ] }, { "cell_type": "markdown", "id": "a0b5191d", "metadata": {}, "source": [ "### 1.1 Partial measurement\n", "\n", "Partial measurement lets you measure only a subset of modes and keep the remaining modes as quantum state vectors. This is useful when you want classical probabilities for some modes while preserving quantum information in the rest. An example where this is useful is the QRNN algorithm that uses mid-circuit measurement to control other quantum operations. It is easy to implement with this feature: another ``QuantumLayer`` that takes for input the quantum state of the unmeasured modes and the measurements outputs is called after the original layer. It can then access all of the information needed to control the quantum state depending on the partial measure. The MerLin reproduction of this paper is available [here](https://github.com/merlinquantum/reproduced_papers/tree/main/papers/QRNN). Another useful application of this new feature would be in a QCNN model where the pooling could be done with partial measurement. Those practical applications motivated the addition of this new feature.\n", "\n", "Below is an example based on a 4-mode circuit. We measure modes [0, 1] out of 4 total modes with 2 photons in the Fock basis. \n", "\n", "For more details, checkout the [partial measurement documentation](../quantum_expert_area/partial_measurement.rst) from which we present the following example." ] }, { "cell_type": "code", "execution_count": 9, "id": "bfdeff40", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "QuantumLayer(custom_circuit, modes=4, input_size=0, output_size=6)\n", " - Output type: \n", "--------- AMPLITUDES (unmeasured modes) ---------\n", " - Amplitudes = [StateVector(tensor=tensor([[ 0.0074-0.3472j, 0.3342+0.4957j, -0.1950-0.6957j]],\n", " grad_fn=), n_modes=2, n_photons=2, _normalized=False), StateVector(tensor=tensor([[-0.7840+0.1447j, 0.6014-0.0526j]], grad_fn=), n_modes=2, n_photons=1, _normalized=False), StateVector(tensor=tensor([[0.4434+0.8963j]], grad_fn=), n_modes=2, n_photons=0, _normalized=False), StateVector(tensor=tensor([[-0.2238+0.7172j, 0.6204+0.2248j]], grad_fn=), n_modes=2, n_photons=1, _normalized=False), StateVector(tensor=tensor([[0.5153-0.8570j]], grad_fn=), n_modes=2, n_photons=0, _normalized=False), StateVector(tensor=tensor([[-0.9923-0.1241j]], grad_fn=), n_modes=2, n_photons=0, _normalized=False)]\n", "\n", " - Amplitudes to tensor = [tensor([[ 0.0074-0.3472j, 0.3342+0.4957j, -0.1950-0.6957j]],\n", " grad_fn=), tensor([[-0.7840+0.1447j, 0.6014-0.0526j]], grad_fn=), tensor([[0.4434+0.8963j]], grad_fn=), tensor([[-0.2238+0.7172j, 0.6204+0.2248j]], grad_fn=), tensor([[0.5153-0.8570j]], grad_fn=), tensor([[-0.9923-0.1241j]], grad_fn=)]\n", "\n", " - Measured modes = (0, 1)\n", "\n", " --------- PROBABILITIES (measured modes) ---------\n", "\n", " - Probabilities (tensor) = tensor([[0.2409, 0.2107, 0.0586, 0.2046, 0.0394, 0.2459]],\n", " grad_fn=)\n" ] } ], "source": [ "from merlin import CircuitBuilder, QuantumLayer\n", "from merlin.core.computation_space import ComputationSpace\n", "from merlin.measurement.strategies import MeasurementStrategy\n", "\n", "# Minimal builder-based circuit definition.\n", "builder = CircuitBuilder(n_modes=4)\n", "builder.add_entangling_layer(trainable=True, name=\"U1\")\n", "\n", "# Partial measurement strategy (measure modes 0 and 1).\n", "strategy = MeasurementStrategy.partial(\n", " modes=[0, 1],\n", " computation_space=ComputationSpace.FOCK,\n", ")\n", "\n", "layer = QuantumLayer(\n", " builder=builder,\n", " n_photons=2,\n", " measurement_strategy=strategy,\n", " return_object=True,\n", ")\n", "\n", "output = layer()\n", "\n", "print(layer)\n", "print(f\" - Output type: {type(output)}\")\n", "print(\"--------- AMPLITUDES (unmeasured modes) ---------\")\n", "print(f\" - Amplitudes = {output.amplitudes}\")\n", "print(f\"\\n - Amplitudes to tensor = {[amp.tensor for amp in output.amplitudes]}\")\n", "print(f\"\\n - Measured modes = {output.measured_modes}\")\n", "print(f\"\\n --------- PROBABILITIES (measured modes) ---------\")\n", "print(f\"\\n - Probabilities (tensor) = {output.probabilities}\")" ] }, { "cell_type": "markdown", "id": "afdd2e38", "metadata": {}, "source": [ "### 1.2 New measurement API\n", "\n", "The ``MeasurementStrategy`` object is now redefined to be more expressive. Indeed, in this object, we now also define the following:\n", "- ``MeasurementStrategy``: It selects how results are extracted from the quantum simulation or hardware backend.\n", "- ``grouping``: ``LexGrouping`` and ``ModGrouping`` provide optional post-processing of outputs. It only works for partial measurement and probabilities.\n", "\n", "Also, the deinition of a ``MeasurementStrategy`` is different. Enum-style and string access is deprecated and will be removed from `MeasurementStrategy` in v0.4. Here is the new way for each option.\n", "\n", "- Deprecated\n", " - Recommended replacement\n", "\n", "\n", "- ``MeasurementStrategy.PROBABILITIES``\n", " - ``MeasurementStrategy.probs(computation_space=...)``\n", "- ``MeasurementStrategy.MODE_EXPECTATIONS``\n", " - ``MeasurementStrategy.mode_expectations(computation_space=...)``\n", "- ``MeasurementStrategy.AMPLITUDES``\n", " - ``MeasurementStrategy.amplitudes(computation_space=...)``\n", "- ``MeasurementStrategy.NONE``\n", " - ``MeasurementStrategy.amplitudes(computation_space=...)``\n", "- ``\"PROBABILITIES\"`` (string)\n", " - ``MeasurementStrategy.probs(computation_space=...)``\n", "\n", "\n", "Lets define a ``QuantumLayer`` that uses a probabilities measurement in the full Fock basis and a LexGrouping strategy. We will define it using the old API and then, with the new one." ] }, { "cell_type": "code", "execution_count": 10, "id": "cd45c030", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Define the circuit\n", "circuit=ml.CircuitBuilder(n_modes=3)\n", "circuit.add_entangling_layer()\n", "circuit.add_angle_encoding([0,1])\n", "circuit.add_entangling_layer()" ] }, { "cell_type": "markdown", "id": "357b0f63", "metadata": {}, "source": [ "Old API, will be deprecated in v.0.4.x." ] }, { "cell_type": "code", "execution_count": null, "id": "9ca4b254", "metadata": {}, "outputs": [], "source": [ "# import torch\n", "\n", "# qlayer=ml.QuantumLayer(\n", "# input_size=2,\n", "# builder=circuit,n_photons=1,\n", "# measurement_strategy=ml.MeasurementStrategy.PROBABILITIES,\n", "# computation_space=ml.ComputationSpace.FOCK,\n", "# )\n", "# qlayer_complete=torch.nn.Sequential(qlayer,ml.LexGrouping(3,2))" ] }, { "cell_type": "markdown", "id": "a11ced31", "metadata": {}, "source": [ "New API" ] }, { "cell_type": "code", "execution_count": 12, "id": "d53593c2", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "qlayer=ml.QuantumLayer(\n", " input_size=2,\n", " builder=circuit,n_photons=1,\n", " measurement_strategy=ml.MeasurementStrategy.probs(\n", " computation_space=ml.ComputationSpace.FOCK,\n", " grouping=ml.LexGrouping(3,2),\n", " ),\n", " )\n", "qlayer.computation_space" ] }, { "cell_type": "markdown", "id": "a748a576", "metadata": {}, "source": [ "## 2. Deprecations\n", "The previous way of defining measurement strategy as presented earlier is deprecated (only a warning will be emitted). \n", "\n", "There is another main deprecation." ] }, { "cell_type": "markdown", "id": "684da259", "metadata": {}, "source": [ "### 2.1 The ``no_bunching`` flag is deprecated\n", "\n", "The ``no_bunching`` flag used in many functions (QuantumLayer and kernels definitions) is deprecated and is removed since version 0.3.0. The new way of deciding to use the unbunched or full Fock computation space is with the ``ComputationSpace`` object in the ``MeasurementStrategy``. Here we present the two ways to define a ``QuantumLayer`` with the equivalent of settng the ``no_bunching`` flag to True or False." ] }, { "cell_type": "code", "execution_count": null, "id": "3223a2da", "metadata": {}, "outputs": [], "source": [ "#Define the basic interferometer\n", "circuit=ml.CircuitBuilder(n_modes=3)\n", "circuit.add_entangling_layer()\n", "circuit.add_angle_encoding([0,1])\n", "circuit.add_entangling_layer()" ] }, { "cell_type": "markdown", "id": "03fb7933", "metadata": {}, "source": [ "Old flag, breaking change" ] }, { "cell_type": "code", "execution_count": null, "id": "bae6e5fa", "metadata": {}, "outputs": [], "source": [ "# # Unbunched space\n", "# qlayer=ml.QuantumLayer(\n", "# input_size=2,\n", "# builder=circuit,n_photons=1,\n", "# no_bunching=True\n", "# )\n", "\n", "# # Full Fock space\n", "# qlayer=ml.QuantumLayer(\n", "# input_size=2,\n", "# builder=circuit,n_photons=1,\n", "# no_bunching=False\n", "# )" ] }, { "cell_type": "markdown", "id": "2fcb1907", "metadata": {}, "source": [ "Equivalents of the flag" ] }, { "cell_type": "code", "execution_count": null, "id": "a1288925", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "ComputationSpace.UNBUNCHED\n", "ComputationSpace.UNBUNCHED\n", "ComputationSpace.FOCK\n" ] } ], "source": [ "#Equivalent of no_bunching=True\n", "qlayer=ml.QuantumLayer(\n", " input_size=2,\n", " builder=circuit,n_photons=1,\n", " measurement_strategy=ml.MeasurementStrategy.probs(\n", " computation_space=ml.ComputationSpace.UNBUNCHED,\n", " ),\n", " )\n", "print(qlayer.computation_space)\n", "\n", "## Or, because the unbunched space is applied by default\n", "qlayer=ml.QuantumLayer(\n", " input_size=2,\n", " builder=circuit,n_photons=1,\n", " )\n", "print(qlayer.computation_space)\n", "\n", "#Equivalent of no_bunching=False\n", "qlayer=ml.QuantumLayer(\n", " input_size=2,\n", " builder=circuit,n_photons=1,\n", " measurement_strategy=ml.MeasurementStrategy.probs(\n", " computation_space=ml.ComputationSpace.FOCK,\n", " ),\n", " )\n", "print(qlayer.computation_space)" ] } ], "metadata": { "kernelspec": { "display_name": "MerLin_dev", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.12" } }, "nbformat": 4, "nbformat_minor": 5 }