{ "cells": [ { "cell_type": "markdown", "id": "dbbfb5b8", "metadata": {}, "source": [ "# MerLin Release 0.4 Highlights\n", "\n", "MerLin 0.4 introduces exciting new features to make your experience even more complete:\n", "\n", "- You can now use NoiseModel (to model indistinguishability, g2, transmittance, brightness, phase error and phase imprecision noises) and differentiate it so you can train better hardware-informed model for better inference on QPU!\n", "- You can use built-in models in MerLin: a photonic QGAN, a Reservoir classifier, a QCNN so you can easily manipulate state-of-the-art quantum models.\n", "- Memristive phase shifters are now usable as a simple method of the ``CircuitBuilder``.\n", "- Also, a new ``EncodingSpace`` object is introduced to easily encode logical state vectors in the full Fock basis.\n", "\n", "This notebook is a quick overview of these new features and it identifies the deprecations and breaking changes of MerLin v.0.4.x" ] }, { "cell_type": "markdown", "id": "9e99c03f", "metadata": {}, "source": [ "## 0. Imports" ] }, { "cell_type": "code", "execution_count": 1, "id": "aac03ae9", "metadata": {}, "outputs": [], "source": [ "import torch\n", "\n", "import matplotlib.pyplot as plt\n", "import merlin as ml\n", "import numpy as np\n", "import perceval as pcvl\n", "\n", "from merlin.core import StateVector\n", "from merlin.datasets import mnist_digits\n", "from sklearn.datasets import load_digits\n", "from sklearn.decomposition import PCA\n", "from sklearn.model_selection import train_test_split" ] }, { "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 ``ReservoirClassifier``\n", "\n", "ReservoirClassifier is the new ready-to-use QORC model. It builds a frozen photonic reservoir from a Haar-random interferometer and trains only a classical linear readout.\n", "\n", "This makes the QORC workflow available as a reusable model instead of requiring users to manually wire preprocessing, reservoir feature extraction, caching, and readout training. The model supports optional scikit-learn dimensionality reduction, reservoir feature normalization, cached embeddings, and direct creation of PyTorch datasets for the readout.\n", "\n", "For more details, check out the [ReservoirClassifier documentation](../../user_guide/models/reservoir_classifier.rst).\n", "\n", "The basic workflow of this new object will be presented as we try to classify the MNIST. Since this dataset contains images that are 28x28, we need to reduce the feature dimension (``in_features``) since it is too big for current interferometers. We will use the ``reduction`` parameter of the ``ReservoirClassifier`` classifier to do so. Indeed, we will use the PCA reduction (available in the ``sklearn`` library) to reduce the images to its 12 most important features." ] }, { "cell_type": "code", "execution_count": 2, "id": "bfdeff40", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Epoch 1: loss=2.5237 and test accuracy of 0.2859\n", "Epoch 2: loss=2.1271 and test accuracy of 0.4185\n", "Epoch 3: loss=1.8060 and test accuracy of 0.5375\n", "Epoch 4: loss=1.5558 and test accuracy of 0.6138\n", "Epoch 5: loss=1.3669 and test accuracy of 0.6508\n", "Epoch 6: loss=1.2268 and test accuracy of 0.6738\n", "Epoch 7: loss=1.1229 and test accuracy of 0.6882\n", "Epoch 8: loss=1.0450 and test accuracy of 0.7047\n", "Epoch 9: loss=0.9854 and test accuracy of 0.7148\n", "Epoch 10: loss=0.9389 and test accuracy of 0.7231\n", "Epoch 11: loss=0.9017 and test accuracy of 0.7324\n", "Epoch 12: loss=0.8711 and test accuracy of 0.7392\n", "Epoch 13: loss=0.8452 and test accuracy of 0.7430\n", "Epoch 14: loss=0.8227 and test accuracy of 0.7498\n", "Epoch 15: loss=0.8025 and test accuracy of 0.7561\n", "Epoch 16: loss=0.7843 and test accuracy of 0.7624\n", "Epoch 17: loss=0.7676 and test accuracy of 0.7674\n", "Epoch 18: loss=0.7525 and test accuracy of 0.7726\n", "Epoch 19: loss=0.7389 and test accuracy of 0.7781\n", "Epoch 20: loss=0.7268 and test accuracy of 0.7823\n", "Test accuracy: 0.7823\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "/var/folders/k_/9bm2xqh95599gcr_s25f45740000gn/T/ipykernel_35913/2286803118.py:53: UserWarning: The given NumPy array is not writable, and PyTorch does not support non-writable tensors. This means writing to this tensor will result in undefined behavior. You may want to copy the array to protect its data or make it writable before converting it to a tensor. This type of warning will be suppressed for the rest of this program. (Triggered internally at /Users/runner/work/pytorch/pytorch/pytorch/torch/csrc/utils/tensor_numpy.cpp:219.)\n", " print(f\"Epoch {epoch+1}: loss={loss.item():.4f} and test accuracy of {(predictions == test_labels).float().mean().item():.4f}\")\n" ] } ], "source": [ "# 0. Get the data\n", "train_features, train_labels, train_metadata = mnist_digits.get_data_train_original()\n", "test_features, test_labels, test_metadata = mnist_digits.get_data_test_original()\n", "\n", "#As vector\n", "train_features = train_features.reshape(train_features.shape[0], -1)\n", "test_features = test_features.reshape(test_features.shape[0], -1)\n", "\n", "# 1. Create the reservoir classifier\n", "reservoir_classifier=ml.ReservoirClassifier(\n", " in_features=train_features.shape[1],\n", " out_features=10,\n", " n_photons=2,\n", " reduction=PCA(\n", " n_components=12,\n", " svd_solver=\"randomized\",\n", " ),\n", " )\n", "\n", "# 2. Call fit reservoir on the training inputs\n", "reservoir_classifier.fit_reservoir(train_features)\n", "\n", "# 3. Generate the reservoir embeddings\n", "train_embeddings = reservoir_classifier.transform_reservoir(train_features)\n", "test_embeddings = reservoir_classifier.transform_reservoir(test_features)\n", "## make_dataset(X, y) can also be used to return a TensorDataset instead\n", "# of a simple tensor\n", "\n", "## If you want the output logits of the reservoir,call predict(X)\n", "\n", "# 4. Train the readout layer\n", "readout = torch.nn.Linear(\n", " train_embeddings.shape[1],\n", " 10,\n", ")\n", "\n", "criterion = torch.nn.CrossEntropyLoss()\n", "optimizer = torch.optim.Adam(readout.parameters(), lr=0.01)\n", "\n", "for epoch in range(20):\n", " optimizer.zero_grad()\n", "\n", " logits = readout(train_embeddings)\n", " loss = criterion(logits, torch.tensor(train_labels))\n", "\n", " loss.backward()\n", " optimizer.step()\n", "\n", " with torch.no_grad():\n", " logits = readout(test_embeddings)\n", " predictions = logits.argmax(dim=1)\n", "\n", " print(f\"Epoch {epoch+1}: loss={loss.item():.4f} and test accuracy of {(predictions == test_labels).float().mean().item():.4f}\")\n", "\n", "\n", "# 5. Evaluate the model\n", "with torch.no_grad():\n", " logits = readout(test_embeddings)\n", " predictions = logits.argmax(dim=1)\n", "\n", "accuracy = (predictions == test_labels).float().mean().item()\n", "\n", "print(f\"Test accuracy: {accuracy:.4f}\")" ] }, { "cell_type": "markdown", "id": "eefc6e58", "metadata": {}, "source": [ "### 1.2 ``PhotonicGenerator``\n", "\n", "`PhotonicGenerator` is a PyTorch module for latent-to-sample generative workflows. It wraps one or more `QuantumLayer` heads and delegates the final\n", "classical interpretation to an output adapter.\n", "\n", "This provides a cleaner base for photonic GAN-style generators. Instead of manually feeding latent variables into a quantum layer and reshaping raw measurement distributions for each experiment, users can define a generator with a latent dimension, one or more quantum heads, and an adapter such as `ImageAdapter` or `VectorAdapter`.\n", "\n", "The generator exposes `sample_latent`, `measure`, `generate`, and normal PyTorch `forward` behavior.\n", "\n", "In practice, this generator is used as part of a QGAN. In this model, a classical discriminator is also used to train the `PhotonicGenerator`: both are trained at the same time. To see the complete pipeline, please consult [this notebook](../../notebooks/reproduced_papers/photonic_QGAN.ipynb). It is based on the original photonic QGAN paper that was reproduced with MerLin.\n", "\n", "For more details on the `PhotonicGenerator` and the QGAN model, check out the [QGAN documentation](../../user_guide/models/qgan.rst).\n", "\n", "Below is some examples on how to instantiate a `PhotonicGenerator`." ] }, { "cell_type": "code", "execution_count": null, "id": "b51113b1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "PhotonicGenerator(\n", " (layers): ModuleList(\n", " (0-1): 2 x QuantumLayer(\n", " (_photon_loss_transform): PhotonLossTransform()\n", " (_detector_transform): DetectorTransform()\n", " (measurement_mapping): Probabilities()\n", " )\n", " )\n", " (output_adapter): ImageAdapter(\n", " (_vector_adapter): VectorAdapter()\n", " )\n", ")\n", "PhotonicGenerator(\n", " (layers): ModuleList(\n", " (0-1): 2 x QuantumLayer(\n", " (_photon_loss_transform): PhotonLossTransform()\n", " (_detector_transform): DetectorTransform()\n", " (measurement_mapping): Probabilities()\n", " )\n", " )\n", " (output_adapter): VectorAdapter()\n", ")\n" ] } ], "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()\n", "\n", "qlayer = ml.QuantumLayer(\n", " input_size=2,\n", " builder=circuit,\n", " n_photons=1,\n", " measurement_strategy=ml.MeasurementStrategy.probs(\n", " computation_space=ml.ComputationSpace.FOCK,\n", " ),\n", ")\n", "\n", "\n", "#Option 1: Same quantum layer repeated for each generator head\n", "generator = ml.PhotonicGenerator(\n", " layers=qlayer,\n", " count=2, #Number of generator heads\n", " # Adapter of the output of the layer to the desired data format. \n", " # Here it takes the layers' ouput and transforms it to an image\n", " output_adapter=ml.ImageAdapter( \n", " shape=(1, 32, 32), #Shape of one image\n", " headwise=True,\n", " normalize_patches=True,\n", " ),\n", ")\n", "print(generator)\n", "\n", "#Option 2: Sequence of layers, each a generator head\n", "# 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()\n", "\n", "qlayer_2 = ml.QuantumLayer(\n", " input_size=2,\n", " builder=circuit,\n", " n_photons=1,\n", " measurement_strategy=ml.MeasurementStrategy.probs(\n", " computation_space=ml.ComputationSpace.FOCK,\n", " ),\n", ")\n", "\n", "\n", "generator = ml.PhotonicGenerator(\n", " layers=[qlayer,qlayer_2], #Sequence of layers each being a generator\n", " # Adapter of the output of the layer to the desired data format. \n", " # Here it takes the layers' ouput and transforms it to a vector f the correct size\n", " output_adapter=ml.VectorAdapter(\n", " size=32 #Desired size\n", " ),\n", ")\n", "print(generator)" ] }, { "cell_type": "markdown", "id": "9596a292", "metadata": {}, "source": [ "### 1.3 ``QCNNClassifier``\n", "\n", "`QCNNClassifier` adds a staged photonic QCNN model for image-like inputs. Architectures are built from validated `QConv`, `QPool`, and `QDense` stages. Users can provide their own stage sequence or rely on the default validated architecture.\n", "\n", "This gives users a higher-level image-classification model without requiring them to assemble every quantum convolution, pooling step, and dense readout by\n", "hand.\n", "\n", "For more details, check out the [QCNN documentation](../../user_guide/models/qcnn.rst).\n", "\n", "Below is an example on how to instantiate a default `QCNNClassifier` and then use it to classify a simple dataset of images 8x8 images of hand-drawn zeros or ones. We will use the default `QCNNClassifier`." ] }, { "cell_type": "code", "execution_count": 4, "id": "3030d9e5", "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "With label: 1\n" ] } ], "source": [ "# 0. Get the data\n", "mnist_x, mnist_y = load_digits(return_X_y=True)\n", "\n", "# Keep only selected classes\n", "mask = np.isin(mnist_y, [0,1])\n", "mnist_x = mnist_x[mask]\n", "mnist_y = mnist_y[mask]\n", "\n", "# Train/test split\n", "train_features, test_features, train_labels, test_labels = train_test_split(\n", " mnist_x, mnist_y, test_size=200,\n", ")# Since there are only 360 data points in this specific dataset with labels = 0 or 1, that implies that we will have 160 training points.\n", "\n", "#transform to tensors\n", "train_features=torch.tensor(train_features)\n", "test_features=torch.tensor(test_features)\n", "train_labels=torch.tensor(train_labels)\n", "test_labels=torch.tensor(test_labels)\n", "\n", "# Reshape to 8×8 images\n", "train_features = train_features.reshape(-1,1, 8, 8)\n", "test_features = test_features.reshape(-1,1, 8, 8)\n", "\n", "\n", "plt.imshow(train_features[0][0], cmap=\"gray\")\n", "plt.axis(\"off\") # hide axes\n", "plt.show()\n", "print(f\"With label: {train_labels[0]}\")" ] }, { "cell_type": "code", "execution_count": 26, "id": "eaceb76b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Epoch 5: loss=0.6913 and test accuracy of 0.5100\n", "Epoch 10: loss=0.6844 and test accuracy of 0.5150\n", "Epoch 15: loss=0.6768 and test accuracy of 0.6600\n", "Epoch 20: loss=0.6682 and test accuracy of 0.9450\n", "Epoch 25: loss=0.6593 and test accuracy of 0.9450\n", "Epoch 30: loss=0.6497 and test accuracy of 0.9550\n", "Epoch 35: loss=0.6392 and test accuracy of 0.9400\n", "Epoch 40: loss=0.6280 and test accuracy of 0.9200\n", "Test accuracy: 0.9200\n" ] } ], "source": [ "# 1. Create the QCNN model\n", "qcnn = ml.QCNNClassifier(\n", " input_shape=(8,8), #Shape of the input\n", " num_classes=2, #Number of classes\n", " #Stages in QCNN classifier, this is the default values when stages=None\n", " stages= [\n", " ml.QCNNClassifier.QConv(kernel_size=2, stride=2),\n", " ml.QCNNClassifier.QPool(kernel_size=2),\n", " ml.QCNNClassifier.QDense()\n", " ],\n", " )\n", "\n", "# 2. Train the model just like any other Pytorch module\n", "criterion = torch.nn.CrossEntropyLoss()\n", "optimizer = torch.optim.Adam(qcnn.parameters(), lr=0.01)\n", "\n", "for epoch in range(40):\n", " optimizer.zero_grad()\n", "\n", " logits = qcnn(train_features)\n", " loss = criterion(logits, train_labels)\n", "\n", " loss.backward()\n", " optimizer.step()\n", "\n", " with torch.no_grad():\n", " logits = qcnn(test_features)\n", " predictions = logits.argmax(dim=1)\n", " if (epoch+1)%5==0:\n", " print(f\"Epoch {epoch+1}: loss={loss.item():.4f} and test accuracy of {(predictions == test_labels).float().mean().item():.4f}\")\n", "\n", "# 3. Evaluate the model\n", "with torch.no_grad():\n", " logits = qcnn(test_features)\n", " predictions = logits.argmax(dim=1)\n", "\n", "accuracy = (predictions == test_labels).float().mean().item()\n", "\n", "print(f\"Test accuracy: {accuracy:.4f}\")\n" ] }, { "cell_type": "markdown", "id": "08c06d17", "metadata": {}, "source": [ "### 1.4 Memristive Phase Shifters\n", "\n", "`CircuitBuilder` now supports memristive phase shifters through\n", "`add_memristive_ps(...)`.\n", "\n", "For machine-learning workflows, this gives a photonic layer a stateful\n", "component that can carry information across repeated calls or timesteps. That is useful for temporal dependence, recurrent-style models, feedback circuits, and sequence experiments where the current circuit response should depend on previous inputs rather than only on the current tensor.\n", "\n", "The builder declares the memristive component, and `QuantumLayer` owns the runtime state, history, serialization hooks, reset behavior, and\n", "`detach_memristive_state(...)` helper. This moves a previously manual feedback layer pattern into the normal builder-layer contract.\n", "\n", "For more information on the use of the memristive phase shifters, please consult the corresponding section in [QuantumLayer's documentation](../../api_reference/api/merlin.algorithms.layer.rst).\n", "\n", "Here is a quick example on how to use a memristive phase shifter." ] }, { "cell_type": "code", "execution_count": 6, "id": "3185ff41", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Memristive state before the forward pass [tensor([0.0100, 0.0100])]\n", "Memristive history before the forward pass [[tensor([0.0100, 0.0100])]]\n", "\n", "Memristive state after the forward pass [tensor([1.0336, 1.1029])]\n", "Memristive history after the forward pass [[tensor([0.0100, 0.0100]), tensor([1.0336, 1.1029])]]\n" ] } ], "source": [ "# 1. Define the update rule that the phase shifter will follow. \n", "# It must have this signature but output may be a typed object\n", "# since it is directly the return of the QuantumLayer.\n", "def update_rule_exp(state: torch.Tensor, output: torch.Tensor):\n", " return torch.exp(state + output[:, 0])\n", "\n", "#2. Create the layer with the circuit builder\n", "circ = ml.CircuitBuilder(n_modes=3)\n", "circ.add_entangling_layer()\n", "# Add a memristive phase shifter\n", "circ.add_memristive_ps(\n", " mode=0, #The mode onto which the memristive phase shifter must be applied\n", " update_rule=update_rule_exp, #The update rule of the phase shifter defined in 1\n", " initial_state=0.01 #The initial value of the phase shifter\n", ")\n", "circ.add_entangling_layer()\n", "circ.add_angle_encoding(modes=[0, 2])\n", "circ.add_entangling_layer()\n", "\n", "#3. Create the quantum layer\n", "ql = ml.QuantumLayer(\n", " builder=circ,\n", " n_photons=3,\n", " measurement_strategy=ml.MeasurementStrategy.probs(\n", " computation_space=ml.ComputationSpace.FOCK\n", " ),\n", ")\n", "#3.5 Reset the memristor to the correct batch size\n", "ql.reset(batch_size=2)\n", "print(f\"Memristive state before the forward pass {ql.memristive_state}\")\n", "print(f\"Memristive history before the forward pass {ql.memristive_history}\")\n", "print()\n", "\n", "#4. Do a forward pass\n", "output=ql(torch.rand((2,2)))\n", "print(f\"Memristive state after the forward pass {ql.memristive_state}\")\n", "print(f\"Memristive history after the forward pass {ql.memristive_history}\")" ] }, { "cell_type": "markdown", "id": "c70b3729", "metadata": {}, "source": [ "### 1.5 Noisy SLOS Simulations\n", "\n", "`QuantumLayer` now supports a noisy SLOS path driven by `pcvl.NoiseModel`. MerLin can account for the main hardware-relevant noise sources directly in simulation\n", "\n", "- brightness;\n", "- transmittance;\n", "- phase imprecision;\n", "- phase error;\n", "- indistinguishability;\n", "- multi-photon emission through `g2`;\n", "- distinguishability of extra `g2` photons.\n", "\n", "For more information on the significance of each noise source, please consult the [following documentation](../../user_guide/noisy_simulations.rst).\n", "\n", "Noisy simulations return probabilities. Source noise and stochastic phase errors are represented as probability mixtures, so MerLin combines probability distributions rather than returning ideal amplitudes.\n", "\n", "For `g2` and source-noise cases, outputs may span multiple photon-number sectors. These outputs are represented through `SectoredDistribution` and `SectorResult`, then converted back into tensors when required by the selected measurement strategy.\n", "\n", "For details on the actual implementation of these noisy simulation please consult [this documentation](../../quantum_expert_area/noisy_simulations.rst)\n", "\n", "#### Why noisy simulations are useful?\n", "\n", "Current photonic processors are noisy. A model trained only with ideal\n", "simulation can therefore learn from output probabilities that are cleaner than the probabilities produced by the hardware.\n", "\n", "Merlin can add the main Quandela hardware noise sources to SLOS simulations through a `pcvl.NoiseModel` object. This makes the training distribution closer to the distribution expected from the QPU.\n", "\n", "#### Simulate the actual results of the Ascella computer\n", "\n", "Here we will compare the perfect simulation of a simple quantum layer to a noisy one and compare the results. Here are the Ascella publicly available noise statistics at the time of the v0.4.0 release\n", "\n", "- ``indistinguishability`` = 0.8636\n", "- ``transmittance`` = 0.0244\n", "- ``g2`` = 0.01955" ] }, { "cell_type": "code", "execution_count": 7, "id": "ec0b5a7f", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "CPLX\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=el_0_li0\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=el_0_lo0\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=el_0_li1\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=el_0_lo1\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=el_0_li2\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=el_0_lo2\n", "\n", "\n", "\n", "\n", "Φ=px1\n", "\n", "\n", "Φ=px2\n", "\n", "CPLX\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=el_1_li0\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=el_1_lo0\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=el_1_li1\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=el_1_lo1\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=el_1_li2\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=el_1_lo2\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "0\n", "1\n", "2\n", "" ], "text/plain": [ "" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "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()\n", "\n", "pcvl.pdisplay(circuit.to_pcvl_circuit(),recursive=True)" ] }, { "cell_type": "code", "execution_count": 8, "id": "01e520ae", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The perfect simulation has output:\n", "Output probability of state (1, 0, 0) is 0.31360045075416565\n", "Output probability of state (0, 1, 0) is 0.31970536708831787\n", "Output probability of state (0, 0, 1) is 0.3666941523551941\n", "\n", "The noisy simulation has output:\n", "Output probability of state (1, 0, 0) is 0.01008016336709261\n", "Output probability of state (0, 0, 0) is 0.9753627181053162\n", "Output probability of state (0, 1, 0) is 0.014294881373643875\n", "Output probability of state (0, 0, 1) is 0.0002563434245530516\n", "Output probability of state (2, 0, 0) is 9.940723657564376e-07\n", "Output probability of state (1, 1, 0) is 2.819427663780516e-06\n", "Output probability of state (1, 0, 1) is 5.0559478381728695e-08\n", "Output probability of state (0, 2, 0) is 1.9991432509414153e-06\n", "Output probability of state (0, 1, 1) is 7.169940374751604e-08\n", "Output probability of state (0, 0, 2) is 6.428759191656752e-10\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "/Users/lfvigneux/Documents/GitHub/merlin/merlin/utils/deprecations.py:545: UserWarning: Noisy simulations with source noise currently use ComputationSpace.FOCK. Other computation spaces are not yet supported for noise models. pcvl.detectors can be used to use custom post-selection.\n", " return func(*f_args, **kwargs)\n" ] } ], "source": [ "#Non noisy layer\n", "qlayer = ml.QuantumLayer(\n", " input_size=2,\n", " builder=circuit,\n", " n_photons=1,\n", " measurement_strategy=ml.MeasurementStrategy.probs(\n", " computation_space=ml.ComputationSpace.UNBUNCHED,\n", " ),\n", ")\n", "\n", "\n", "# Noisy layer\n", "## Option 1 pass the noise to a perceval experiment and then to the layer\n", "experiment=pcvl.Experiment(\n", " m_circuit=circuit.to_pcvl_circuit(),\n", " noise=pcvl.NoiseModel(\n", " indistinguishability=0.8636,\n", " transmittance=0.0244,g2=0.01955\n", " )\n", " )\n", "noisy_qlayer = ml.QuantumLayer(\n", " input_size=2,\n", " experiment=experiment,\n", " n_photons=1,\n", " measurement_strategy=ml.MeasurementStrategy.probs(\n", " computation_space=ml.ComputationSpace.UNBUNCHED,\n", " ),\n", " input_parameters=[\"px\"],\n", " trainable_parameters=[\"el\"],\n", ")\n", "## Option 2 pass the noise to the noise argument\n", "noisy_qlayer = ml.QuantumLayer(\n", " input_size=2,\n", " builder=circuit,\n", " n_photons=1,\n", " measurement_strategy=ml.MeasurementStrategy.probs(\n", " computation_space=ml.ComputationSpace.UNBUNCHED,\n", " ),\n", " noise=pcvl.NoiseModel(\n", " indistinguishability=0.8636,\n", " transmittance=0.0244,\n", " g2=0.01955\n", " )\n", ")\n", "\n", "#Run the layers\n", "non_noisy_output=qlayer(torch.tensor([0.5,1.1]))\n", "noisy_output=noisy_qlayer(torch.tensor([0.5,1.1]))\n", "\n", "print(f\"The perfect simulation has output:\")\n", "for key, prob in zip(qlayer.output_keys, non_noisy_output.flatten()):\n", " print(f\"Output probability of state {key} is {prob}\")\n", "print()\n", "\n", "print(f\"The noisy simulation has output:\")\n", "for key, prob in zip(noisy_qlayer.output_keys, noisy_output.flatten()):\n", " print(f\"Output probability of state {key} is {prob}\")" ] }, { "cell_type": "markdown", "id": "8ca31794", "metadata": {}, "source": [ "We observe that the outputs are very different. So training locally your quantum models with noise models that are similar to the actual QPU will be useful to get meaningful QPU results." ] }, { "cell_type": "markdown", "id": "96db5275", "metadata": {}, "source": [ "### 1.6 New ``EncodingSpace`` Object\n", "\n", "`EncodingSpace` describes how the last dimension of an amplitude tensor maps into MerLin's canonical Fock basis. It makes logical amplitude inputs explicit and removes the need to manually enumerate Fock states for common encodings.\n", "\n", "Supported encodings include:\n", "\n", "- `EncodingSpace.FOCK` for amplitudes already in full Fock order;\n", "- `EncodingSpace.UNBUNCHED` for collision-free photonic states;\n", "- `EncodingSpace.DUAL_RAIL` for qubit-like binary logical states;\n", "- partitioned encodings through `EncodingSpace(modes_per_photon=[...])`;\n", "- QLOQ-style grouped encodings through `EncodingSpace.qloq(...)`.\n", "\n", "`StateVector.from_tensor(..., encoding=...)` validates the logical tensor, embeds it into Fock order, and returns a `StateVector` that can be passed to `QuantumLayer.forward()`.\n", "\n", "For more details, consult the [API documentation](../../api_reference/api/merlin.core.encoding_space.rst)\n", "\n", "Here is an example of the usage for a tensor in the unbunched space of 2 photons and 4 modes." ] }, { "cell_type": "code", "execution_count": 9, "id": "1faeffbd", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "tensor([0.1048, 0.2097, 0.3145, 0.4193, 0.5241, 0.6290], dtype=torch.float64)\n", "tensor([0.0000+0.j, 0.1048+0.j, 0.2097+0.j, 0.3145+0.j, 0.0000+0.j, 0.4193+0.j, 0.5241+0.j,\n", " 0.0000+0.j, 0.6290+0.j, 0.0000+0.j])\n" ] } ], "source": [ "#1. Define the tensor in the logical space\n", "#There is 6 basis states in the unbunched space of 2 photons and 4 modes\n", "unbunched_tensor=torch.arange(1,7,dtype=torch.float64)\n", "unbunched_tensor=unbunched_tensor/torch.norm(unbunched_tensor)\n", "print(unbunched_tensor)\n", "\n", "#2. Encode it into a state vector with the EncodingSpace object\n", "encoded_state=StateVector.from_tensor(tensor=unbunched_tensor,encoding=ml.EncodingSpace.UNBUNCHED,n_modes=4,n_photons=2)\n", "print(encoded_state.tensor)" ] }, { "cell_type": "markdown", "id": "8060bc39", "metadata": {}, "source": [ "### 1.7 MerlinProcessor Local Processor Support\n", "\n", "\n", "`MerlinProcessor` now accepts Perceval `AProcessor` objects through the keyword-only `processor=` argument.\n", "\n", "This means local Perceval processors can be used from the same MerLin execution interface as remote processors and sessions. Local processors use a local backend route. `RemoteProcessor` instances passed through `processor=` are normalized to the remote backend route. The older `remote_processor=` argument still works as a deprecated compatibility path.\n", "\n", "This is useful when users want to evaluate MerLin models on local Perceval backends, including local noisy or sampling backends, without leaving the MerLin model execution workflow.\n", "\n", "Check out the corresponding deprecation section (3.1) explaining the migration." ] }, { "cell_type": "markdown", "id": "f7d6cb0b", "metadata": {}, "source": [ "### 1.8 Reclarified kernel contract\n", "\n", "The kernel has been restructured to be built directly on ``QuantumLayer``s instead of a completely different backbone. That way, the updates and changes of the ``QuantumLayer`` are directly applied to the kernels. Here are the new contracts.\n", "\n", "- `FeatureMap` – a descriptor that stores\n", "\t the photonic circuit and its parameter layout. It accepts:\n", "\n", "\t - a `pcvl.Circuit` (manual construction),\n", "\t - a `CircuitBuilder` (declarative), or\n", "\t - a `pcvl.Experiment` (unitary circuit + measurement semantics).\n", "\n", "\n", "- `FidelityKernel` – validates the feature map, builds the intrinsic ``QuantumLayer``, normalizes public inputs, and delegates kernel-matrix construction to the backend.\n", "\n", "\n", "The compatibility methods such as `KernelCircuitBuilder`, `FidelityKernel.simple(...)` and simple-factory compatibility arguments are deprecated. Indeed, you can now use the regular ``CircuitBuilder`` to build your feature map as `FeatureMap` is now defined as the main contract to define the circuit. Therefore, circuits are defined in ``FeatureMap`` and then, this is passed to the ``FidelityKernel``.\n", "\n", "As you can now build a circuit using the builder in the `FeatureMap`, it is not necessary to use the ``KernelCircuitBuilder``!" ] }, { "cell_type": "markdown", "id": "6d13ef8d", "metadata": {}, "source": [ "### 1.9 StateVector and Sparse State Workflows\n", "\n", "`StateVector` and probability-distribution objects have improved support for sparse tensors and logical-to-Fock mappings. This helps amplitude-input workflows stay memory-aware, especially when the logical state is much smaller than the full Fock basis." ] }, { "cell_type": "markdown", "id": "a748a576", "metadata": {}, "source": [ "## 2. Breaking Changes\n", "Here are features that are now completely removed from v.0.4." ] }, { "cell_type": "markdown", "id": "684da259", "metadata": {}, "source": [ "### 2.1 The ``no_bunching`` Flag Is Now Removed\n", "\n", "The ``no_bunching`` flag used in version 0.1 and 0.2 in many functions (QuantumLayer and kernels definitions) was deprecated since version 0.3.0 and is now removed. 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": 10, "id": "3223a2da", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "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": 11, "id": "bae6e5fa", "metadata": {}, "outputs": [], "source": [ "# # Unbunched space\n", "# qlayer=ml.QuantumLayer(\n", "# input_size=2,\n", "# builder=circuit,\n", "# 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", "# n_photons=1,\n", "# no_bunching=False\n", "# )" ] }, { "cell_type": "markdown", "id": "2fcb1907", "metadata": {}, "source": [ "Equivalents of the flag" ] }, { "cell_type": "code", "execution_count": 12, "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", " 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", " 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", " n_photons=1,\n", " measurement_strategy=ml.MeasurementStrategy.probs(\n", " computation_space=ml.ComputationSpace.FOCK,\n", " ),\n", ")\n", "print(qlayer.computation_space)" ] }, { "cell_type": "markdown", "id": "5b4c0db0", "metadata": {}, "source": [ "### 2.2 Constructor ``computation_space`` Is Removed\n", "\n", "The legacy ``computation_space`` in the constrictor of the ``QuantumLayer`` is now removed. It was deprecated in v.0.3 and is now removed in v.0.4.\n", "\n", "Use explicit computation-space configuration instead:" ] }, { "cell_type": "code", "execution_count": 13, "id": "6618b6a5", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "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": "c3d9b7da", "metadata": {}, "source": [ "Old flag, breaking change" ] }, { "cell_type": "code", "execution_count": 14, "id": "4ce8557a", "metadata": {}, "outputs": [], "source": [ "# # Unbunched space\n", "# qlayer=ml.QuantumLayer(\n", "# input_size=2,\n", "# builder=circuit,\n", "# n_photons=1,\n", "# computation_space=ml.ComputationSpace.UNBUNCHED\n", "# )\n", "\n", "# # Full Fock space\n", "# qlayer=ml.QuantumLayer(\n", "# input_size=2,\n", "# builder=circuit,\n", "# n_photons=1,\n", "# computation_space=ml.ComputationSpace.FOCK,\n", "# )" ] }, { "cell_type": "markdown", "id": "c75def67", "metadata": {}, "source": [ "Equivalents of the flag" ] }, { "cell_type": "code", "execution_count": 15, "id": "a9b14da5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "ComputationSpace.UNBUNCHED\n", "ComputationSpace.FOCK\n" ] } ], "source": [ "# Equivalent of computation_space=ml.ComputationSpace.UNBUNCHED\n", "qlayer = ml.QuantumLayer(\n", " input_size=2,\n", " builder=circuit,\n", " n_photons=1,\n", " measurement_strategy=ml.MeasurementStrategy.probs(\n", " computation_space=ml.ComputationSpace.UNBUNCHED,\n", " ),\n", ")\n", "print(qlayer.computation_space)\n", "\n", "# Equivalent of computation_space=ml.ComputationSpace.FOCK\n", "qlayer = ml.QuantumLayer(\n", " input_size=2,\n", " builder=circuit,\n", " n_photons=1,\n", " measurement_strategy=ml.MeasurementStrategy.probs(\n", " computation_space=ml.ComputationSpace.FOCK,\n", " ),\n", ")\n", "print(qlayer.computation_space)" ] }, { "cell_type": "markdown", "id": "c8b12a88", "metadata": {}, "source": [ "### 2.3 Legacy MeasurementStrategy Access Is Removed\n", "\n", "Legacy enum-style measurement access such as MeasurementStrategy.PROBABILITIES, MeasurementStrategy.MODE_EXPECTATIONS, and MeasurementStrategy.AMPLITUDES now fails with a migration error. Passing measurement strategies as strings, such as \"PROBABILITIES\", is also no longer supported.\n", "\n", "The accepted way is now to use ``MeasurementStrategy`` factory methods:\n", "\n", "- ``MeasurementStrategy.probs(...)``\n", "- ``MeasurementStrategy.mode_expectations(...)``\n", "- ``MeasurementStrategy.amplitudes(...)``\n", "- ``MeasurementStrategy.partial(...)``\n", "\n", "Here are the suggested replacements\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", "We will present the migration for the probabilities strategy." ] }, { "cell_type": "code", "execution_count": 16, "id": "2e13bda9", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "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": "a34daa56", "metadata": {}, "source": [ "Old accesses, breaking change" ] }, { "cell_type": "code", "execution_count": 17, "id": "7532ca4c", "metadata": {}, "outputs": [], "source": [ "# # Enum-style access\n", "# qlayer=ml.QuantumLayer(\n", "# input_size=2,\n", "# builder=circuit,\n", "# n_photons=1,\n", "# measurement_strategy=ml.MeasurementStrategy.PROBABILITIES\n", "# )\n", "\n", "# # String access\n", "# qlayer=ml.QuantumLayer(\n", "# input_size=2,\n", "# builder=circuit,\n", "# n_photons=1,\n", "# measurement_strategy=\"PROBABILITIES\"\n", "# )" ] }, { "cell_type": "markdown", "id": "fe09248e", "metadata": {}, "source": [ "Equivalents of the flag" ] }, { "cell_type": "code", "execution_count": null, "id": "d43b7f15", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MeasurementStrategy(type=, measured_modes=(), computation_space=, grouping=None, occupancy_readout=False)\n" ] } ], "source": [ "qlayer = ml.QuantumLayer(\n", " input_size=2,\n", " builder=circuit,\n", " n_photons=1,\n", " measurement_strategy=ml.MeasurementStrategy.probs(),\n", ")\n", "print(qlayer.measurement_strategy)" ] }, { "cell_type": "markdown", "id": "a20f5413", "metadata": {}, "source": [ "### 2.4 Constructor Amplitude Encoding Removed\n", "\n", "``QuantumLayer(..., amplitude_encoding=True)`` is no longer accepted.\n", "\n", "Amplitude inputs should now be passed to ``QuantumLayer.forward()`` as either a ``StateVector`` or a complex tensor.\n", "\n", "Let's present these alternatives." ] }, { "cell_type": "code", "execution_count": 19, "id": "0af902d6", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Define the basic interferometer\n", "circuit = ml.CircuitBuilder(n_modes=3)\n", "circuit.add_entangling_layer()" ] }, { "cell_type": "markdown", "id": "35630211", "metadata": {}, "source": [ "Old flag, breaking change" ] }, { "cell_type": "code", "execution_count": null, "id": "d07eb7a5", "metadata": {}, "outputs": [], "source": [ "# qlayer=ml.QuantumLayer(\n", "# input_size=2,\n", "# builder=circuit,\n", "# n_photons=1,\n", "# amplitude_encoding=True\n", "# )" ] }, { "cell_type": "markdown", "id": "3ada6d98", "metadata": {}, "source": [ "Equivalents of the flag" ] }, { "cell_type": "code", "execution_count": 21, "id": "fc25f3b3", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "tensor([[0.0068, 0.6490, 0.3442]], grad_fn=)" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "qlayer=ml.QuantumLayer(\n", " builder=circuit,\n", " n_photons=1,\n", " )\n", "\n", "# Option 1: StateVector input\n", "input_state = StateVector.from_tensor(\n", " tensor=torch.rand(1, 3),\n", " n_modes=3,\n", " n_photons=1,\n", " encoding=ml.EncodingSpace.FOCK,\n", ")\n", "qlayer(input_state)\n", "# Option 2: complex tensor\n", "input_state = torch.rand(1, qlayer.output_size, dtype=torch.complex64)\n", "\n", "qlayer(input_state)" ] }, { "cell_type": "markdown", "id": "61d76f0d", "metadata": {}, "source": [ "### 2.5 Tensor Constructor ``input_state`` Removed\n", "\n", "Passing a raw ``torch.Tensor`` as constructor input_state is no longer accepted. If a state object is needed at construction time, build it explicitly with ``StateVector.from_tensor(...)``.\n", "\n", "Here is exactly how to migrate from the previous API." ] }, { "cell_type": "code", "execution_count": 22, "id": "9b01a8e3", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "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": "391b0f0e", "metadata": {}, "source": [ "Old flag, breaking change" ] }, { "cell_type": "code", "execution_count": 23, "id": "a61b2d4a", "metadata": {}, "outputs": [], "source": [ "# # Tensor input state\n", "# amplitudes=torch.rand(2)\n", "# qlayer=ml.QuantumLayer(\n", "# input_size=3,\n", "# builder=circuit,\n", "# n_photons=1,\n", "# input_state=amplitudes\n", "# )" ] }, { "cell_type": "markdown", "id": "4f67eba5", "metadata": {}, "source": [ "Equivalents of the flag" ] }, { "cell_type": "code", "execution_count": 24, "id": "0b9ad8c5", "metadata": {}, "outputs": [], "source": [ "# StateVector input\n", "amplitudes=torch.rand(3)\n", "input_state=StateVector.from_tensor(\n", " amplitudes,\n", " n_modes=3,\n", " n_photons=1,\n", " encoding=ml.EncodingSpace.UNBUNCHED,\n", " )\n", "\n", "qlayer=ml.QuantumLayer(\n", " input_size=2,\n", " builder=circuit,n_photons=1,\n", " input_state=input_state\n", " )" ] }, { "cell_type": "markdown", "id": "b5b2eaa0", "metadata": {}, "source": [ "## 3. Deprecations\n", "\n", "Two new deprecations are effective since version v.0.4." ] }, { "cell_type": "markdown", "id": "aee5138c", "metadata": {}, "source": [ "### 3.1 ``remote_processor`` Argument of the ``MerlinProcessor`` Is Deprecated\n", "\n", "The name of the previous ``remote_processor`` argument is now simply ``processor`` where you pass the same Perceval processor." ] }, { "cell_type": "code", "execution_count": 25, "id": "82647a52", "metadata": {}, "outputs": [], "source": [ "#Deprecated\n", "# local_processor = pcvl.Processor(\"SLOS\")\n", "# proc = ml.MerlinProcessor(remote_processor=local_processor)\n", "\n", "#New method\n", "local_processor = pcvl.Processor(\"SLOS\")\n", "proc = ml.MerlinProcessor(processor=local_processor)" ] }, { "cell_type": "markdown", "id": "bba8e086", "metadata": {}, "source": [ "### 3.2 Kernel Compatibility Helpers Are Deprecated\n", "\n", "As it was identifed in the section 1.8, kernel compatibility helpers such as ``KernelCircuitBuilder``, ``FidelityKernel.simple(...)``, and related simple-factory compatibility arguments are deprecated and kept only for transition." ] }, { "cell_type": "markdown", "id": "69597465", "metadata": {}, "source": [ "## 4. Compatibility and Maintenance\n", "\n", "MerLin 0.4 updates the dependency floor to ``perceval-quandela>=1.2.1`` and allows newer ``PyTorch`` versions up to ``torch<=2.12.0``.\n", "\n", "The test suite has been expanded substantially around noisy SLOS, g2 behavior, encoding spaces, state-vector inputs, deprecation removals, ``MerlinProcessor`` local execution, photonic generators, QCNNs, and QORC reservoir workflows.\n", "\n", "### Note for the user: \n", "\n", "To move your models or ``QuantumLayer``s between cpu and gpu, please use the ``.to(...)`` method instead of ``.cuda()`` or ``.cpu()``. Only the ``.to(...)`` method has been optimized for a safe moving between device." ] } ], "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 }