### Running the Simulation with MPSBackend Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/getting_started.ipynb This snippet executes the simulation using the `emu-mps` backend. It instantiates the `MPSBackend` class, providing the previously created Pulser `seq`uence and the `mpsconfig`uration object. The `run` method is then called on the backend instance, which starts the time evolution simulation and returns a results object containing the computed observables. ```python sim = MPSBackend(seq, config=mpsconfig) results = sim.run() ``` -------------------------------- ### Creating MPSConfig Object Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/getting_started.ipynb This code creates the main configuration object for the `emu-mps` backend by instantiating the `MPSConfig` class. It consolidates the simulation time step `dt` and a list of all desired observable instances (`bitstrings`, `fidelity_mps_pure`, `density`) to be computed during the simulation run. ```python mpsconfig = MPSConfig( dt=dt, observables=[ bitstrings, fidelity_mps_pure, density, ], ) ``` -------------------------------- ### Importing Libraries for Simulation Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/getting_started.ipynb This snippet imports all necessary libraries and modules required for setting up the Pulser sequence, configuring the `emu-mps` backend, running the simulation, and visualizing the results. It includes components from `matplotlib`, `emu_mps` (for backend, configuration, and observables), `utils_examples` (for helper functions), `pulser` (for device, register, and sequence), and `numpy` (for numerical operations). ```python import matplotlib.pyplot as plt from emu_mps import ( MPS, MPSConfig, MPSBackend, BitStrings, Fidelity, Occupation, ) from utils_examples import afm_sequence_from_register, square_perimeter_points import pulser from pulser.devices import AnalogDevice import numpy as np ``` -------------------------------- ### Installing Documentation Dependencies using pip Source: https://github.com/pasqal-io/emulators/blob/main/docs/CONTRIBUTING.md This command installs all necessary Python packages required to build the project's documentation. These dependencies are listed in the `doc_requirements.txt` file. ```shell pip install -r doc_requirements.txt ``` -------------------------------- ### Listing Available Result Tags Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/getting_started.ipynb After the simulation has completed, this snippet demonstrates how to inspect the contents of the `results` object. Calling `results.get_result_tags()` returns a list of strings, where each string is a unique identifier or tag corresponding to one of the observables that were configured and computed during the simulation. ```python results.get_result_tags() ``` -------------------------------- ### Configuring BitStrings Observable Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/getting_started.ipynb This snippet creates an instance of the `BitStrings` observable for `emu-mps`. This observable enables sampling of the quantum state at specified time points, returning counts for different bitstring outcomes. It is configured with the list of `evaluation_times` and the number of `num_shots` to perform for sampling. ```python sampling_times = 1000 bitstrings = BitStrings(evaluation_times=eval_times, num_shots=sampling_times) ``` -------------------------------- ### Installing Test Dependencies and Running Pytest Source: https://github.com/pasqal-io/emulators/blob/main/docs/CONTRIBUTING.md These commands install the dependencies required specifically for running tests from the `test_requirements.txt` file. Afterwards, the `pytest` command is executed to run the automated test suite for the project. ```shell pip install -r test_requirements.txt pytest ``` -------------------------------- ### Setting Up and Running Pre-Commit Hooks and Pytest Source: https://github.com/pasqal-io/emulators/blob/main/docs/CONTRIBUTING.md This sequence installs the pre-commit tool, sets up its hooks in the local Git repository, runs the hooks against all files to ensure code style and quality standards are met, and finally runs the unit tests with pytest. ```shell pip install pre-commit pre-commit install pre-commit run --all-files pytest ``` -------------------------------- ### Setting emu-mps Simulation Parameters Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/getting_started.ipynb This code defines fundamental simulation parameters for the `emu-mps` backend. `dt` sets the time step for the simulation's numerical integration. `eval_times` specifies the time points (relative to the sequence duration) at which observables should be computed. `basis` sets the measurement basis, which determines how bitstrings and other state properties are interpreted. ```python dt = 100 eval_times = [1.0] basis = ("r","g") ``` -------------------------------- ### Importing Libraries and Checking Version - Python Source: https://github.com/pasqal-io/emulators/blob/main/examples/emu_mps/optimatrix/1d_system.ipynb Imports core libraries for quantum simulation (`pulser`), numerical operations (`numpy`), custom helpers, and the optimization module (`emu_mps.optimatrix`). It also displays the version of the `pulser` library. ```python import pulser import numpy as np import helpers as helpers import emu_mps.optimatrix as optimatrix pulser.__version__ ``` -------------------------------- ### Analyzing Bitstring Results Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/getting_started.ipynb This code block retrieves, analyzes, and visualizes the bitstring sampling results from the `results` object. It gets the evaluation times, retrieves the bitstring counts at the final time point, identifies the most frequent bitstring, and then filters and plots the bitstrings with counts above a threshold as a histogram using matplotlib. ```python results.get_result_times(bitstrings) bitstrings_final = results.get_result(bitstrings, 1.0) max_val = max(bitstrings_final.values()) # max number of counts in the bitstring max_string = [key for key, value in bitstrings_final.items() if value == max_val] print( "The most frequent bitstring is {} which was sampled {} times".format( max_string, max_val ) ) filtered_counts = [count for count in bitstrings_final.values() if count > 20] filtered_bitstrings = [ bitstring for bitstring, count in bitstrings_final.items() if count > 20 ] x_labels = range(len(filtered_bitstrings)) with plt.style.context("seaborn-v0_8-darkgrid"): fig, ax = plt.subplots() ax.bar(x_labels, filtered_counts, color="teal", alpha=0.8) ax.set_xlabel("Bitstrings") ax.set_ylabel("Counts") ax.set_title("Histogram of Bitstring Counts (Counts > 20)") ax.set_xticks(x_labels) ax.set_xticklabels(filtered_bitstrings, rotation="vertical") ax.spines["top"].set_visible(False) ax.spines["right"].set_visible(False) plt.tight_layout() plt.show() ``` -------------------------------- ### Installing Pasqal Emulators for Local Development using pip Source: https://github.com/pasqal-io/emulators/blob/main/docs/CONTRIBUTING.md This command installs the current package in 'editable' mode into your Python environment using pip. The '-e' flag means that changes made to the source code files will be immediately reflected without needing to reinstall. ```shell pip install -e . ``` -------------------------------- ### Visualizing Optimized Register - Python Source: https://github.com/pasqal-io/emulators/blob/main/examples/emu_mps/optimatrix/1d_system.ipynb Displays the graphical representation of the `new_chain` register, which has been reordered based on the optimal permutation calculated to minimize the interaction matrix bandwidth. ```python new_chain.draw() ``` -------------------------------- ### Configuring Fidelity Observable Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/getting_started.ipynb This code configures the `Fidelity` observable to calculate the overlap between the evolving quantum state and a specific target state. It first defines the target state as a dictionary of amplitudes (`afm_string_pure`), converts it into an `MPS` object, and then instantiates the `Fidelity` observable, providing the `evaluation_times` and the target `state`. ```python nqubits = len(seq.register.qubit_ids) afm_string_pure = {"rgrgrgrg": 1.0} afm_mps_state = MPS.from_state_amplitudes( eigenstates=basis, amplitudes=afm_string_pure ) fidelity_mps_pure = Fidelity(evaluation_times=eval_times, state=afm_mps_state) ``` -------------------------------- ### Example MPO Construction for 5 Atoms with SymPy Source: https://github.com/pasqal-io/emulators/blob/main/docs/developer/ising_MPO.ipynb This Python code demonstrates how to construct the MPO for a 5-atom Rydberg Hamiltonian. It defines the single-site terms and the interaction matrix and calls `general_make_H` to get the MPO factors (S, L1, M, R1, E), which are then displayed. ```python single_terms = [A(0),A(1),A(2),A(3),A(4)] inter_matri = general_interaction_matrix(5) cores = general_make_H(inter_matri,single_terms) print("Matrix S:") display(cores[0]) display(Latex(f"Matrix $L_1$:)) display(cores[1]) print("Matrix M:") display(cores[2]) display(Latex(f"Matrix $R_1$:)) display(cores[3]) print("Matrix E:") display(cores[4]) ``` -------------------------------- ### Defining Physical and Sequence Parameters Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/getting_started.ipynb This code defines the key physical parameters and sequence timings required to generate the Pulser sequence for the antiferromagnetic state preparation. These include the peak laser amplitude (`Omega_max`), initial and final detunings (`delta_0`, `delta_f`), rise and fall phase durations (`t_rise`, `t_fall`), a sweep factor determining the sweep duration, the side length of the square perimeter for atom placement, and the calculated Rydberg blockade radius relative to the laser parameters. ```python Omega_max = 2 * 2 * np.pi delta_0 = -6 * Omega_max / 2 delta_f = 1 * Omega_max / 2 t_rise = 500 t_fall = 1500 sweep_factor = 2 square_length = 3 R_interatomic = AnalogDevice.rydberg_blockade_radius(Omega_max / 2) ``` -------------------------------- ### Configuring Occupation Observable Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/getting_started.ipynb This snippet creates an instance of the `Occupation` observable, which measures the local state density or 'magnetization' at each qubit site. It is configured to measure the occupation across the entire duration of the pulse sequence at intervals determined by the simulation time step `dt`. The evaluation times are calculated relative to the total sequence duration. ```python density = Occupation( evaluation_times=[x/seq.get_duration() for x in range(0, seq.get_duration(), dt)] ) ``` -------------------------------- ### Generating Pulser Sequence Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/getting_started.ipynb Using the defined physical parameters and the created Pulser register, this code generates the pulse sequence designed to prepare the antiferromagnetic state. It calls the `afm_sequence_from_register` helper function, passing the register, timing, and amplitude/detuning parameters. The resulting `Sequence` object, which encapsulates the detailed laser pulses, is then visualized using the `draw` method. ```python seq = afm_sequence_from_register( reg, Omega_max, delta_0, delta_f, t_rise, t_fall, sweep_factor, AnalogDevice ) seq.draw("input") ``` -------------------------------- ### Retrieving Magnetization Data Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/getting_started.ipynb This code retrieves the computed magnetization (occupation) values and their corresponding time points from the `results` object. `magnetization_values` is an array where each row corresponds to a time step and each column corresponds to a qubit's density value. `magnetization_times` is a list of times at which these occupation values were recorded. ```python magnetization_values = np.array(list(results.occupation)) magnetization_times = results.get_result_times(density) ``` -------------------------------- ### Analyzing Fidelity Results Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/getting_started.ipynb This snippet retrieves and prints the fidelity value computed against the target pure antiferromagnetic state. It fetches the fidelity result at the final evaluation time using `results.get_result` and then calculates and displays both the complex fidelity value and its squared magnitude, which represents the probability of finding the system in the target state. ```python fidelity_pure = results.get_result(fidelity_mps_pure,1.0) print( "The fidelity computed for the system final state against the pure state |rgrgrgr> is {}.\nThe probability of the system being in that sate is equal to {} ".format( fidelity_pure, abs(fidelity_pure) ** 2 ) ) ``` -------------------------------- ### Visualizing Original and Shuffled Registers - Python Source: https://github.com/pasqal-io/emulators/blob/main/examples/emu_mps/optimatrix/1d_system.ipynb Uses the `.draw()` method to display the graphical representation of both the initial `original_chain` register and the `chain` register after shuffling, allowing visual comparison of their layouts. ```python original_chain.draw() chain.draw() ``` -------------------------------- ### Installing and Importing Sympy Libraries (Python) Source: https://github.com/pasqal-io/emulators/blob/main/docs/developer/ising_MPO.ipynb Installs the Sympy library using pip and imports necessary modules for symbolic mathematical operations, including TensorProduct for Kronecker products and display/Latex for rendering equations in environments like Jupyter notebooks. Sympy is a core dependency for defining and manipulating symbolic operators. ```python !pip install sympy import sympy as sp from sympy.physics.quantum import TensorProduct as TP from IPython.display import display, Latex ``` -------------------------------- ### Importing Required Packages - Python Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/noise.ipynb This snippet imports the necessary Python libraries for defining quantum sequences (Pulser), simulating with MPS (emu-mps), numerical operations (NumPy), and controlling logging output (logging). These are prerequisites for running the simulation setup. ```python import pulser import emu_mps import numpy as np import logging #used to turn of logging in emu_mps ``` -------------------------------- ### Optimizing Chain Ordering via Bandwidth Minimization - Python Source: https://github.com/pasqal-io/emulators/blob/main/examples/emu_mps/optimatrix/1d_system.ipynb Calculates the reciprocal distance interaction matrix for the shuffled `chain`. It then uses `optimatrix.minimize_bandwidth` to find an optimal qubit permutation that minimizes bandwidth and applies this permutation to create a reordered register `new_chain`. ```python interactions = helpers.reciprocal_dist_matrix(chain) optimal_permutation = optimatrix.minimize_bandwidth(interactions, 100) print("optimal permutation is\n", optimal_permutation) new_chain = helpers.permute_sequence_registers(chain, optimal_permutation) ``` -------------------------------- ### Creating Pulser Register Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/getting_started.ipynb This snippet creates a Pulser `Register` object representing the arrangement of neutral atoms. It first calculates the 2D coordinates for 8 atoms positioned along the perimeter of a square using the `square_perimeter_points` helper function, scaling them by the calculated interatomic distance `R_interatomic`. It then instantiates the `Register` from these coordinates and visualizes the register layout, including the blockade radius and interaction graph. ```python coords = R_interatomic * square_perimeter_points(square_length) reg = pulser.Register.from_coordinates(coords) reg.draw(blockade_radius=R_interatomic, draw_graph=True, draw_half_radius=True) ``` -------------------------------- ### Plotting Time Evolution of Magnetization Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/getting_started.ipynb This snippet visualizes the time evolution of the state density (magnetization) for each qubit. It uses matplotlib's `pcolormesh` to create a heatmap where the x-axis represents time, the y-axis represents the qubit index, and the color intensity indicates the occupation value. This plot shows how the local state density changes on each atom site throughout the pulse sequence. ```python fig, ax = plt.subplots(figsize=(8, 4), layout="constrained") num_time_points, positions = magnetization_values.shape x, y = np.meshgrid(np.arange(num_time_points), np.arange(1, positions + 1)) im = plt.pcolormesh(magnetization_times, y, magnetization_values.T, shading="auto") ax.set_xlabel("Time [ns]") ax.set_ylabel("Qubit") ax.set_title("State Density") ax.set_yticks(np.arange(1, positions + 1)) cb = fig.colorbar(im, ax=ax) ``` -------------------------------- ### Creating and Shuffling 1D Pulser Register - Python Source: https://github.com/pasqal-io/emulators/blob/main/examples/emu_mps/optimatrix/1d_system.ipynb Defines `one_dim_systems_coords` to generate a linear Pulser register of specified length `L`. It then uses this function to create an initial register `original_chain` and a shuffled version `chain` using a helper function. ```python def one_dim_systems_coords(L: int) -> np.ndarray: reg = pulser.register.register.Register.rectangle(1, L, spacing=4.0, prefix=None) return reg original_chain = one_dim_systems_coords(6) chain = helpers.shuffle_qubits(original_chain) ``` -------------------------------- ### Example MPO Construction for 3 Atoms with SymPy Source: https://github.com/pasqal-io/emulators/blob/main/docs/developer/ising_MPO.ipynb This Python code demonstrates how to construct the MPO for a 3-atom Rydberg Hamiltonian. It defines the single-site terms and the interaction matrix using the helper functions and then calls `general_make_H` to obtain the MPO factors (S, M, E), which are then printed. ```python single_terms = [A(0),A(1),A(2)] inter_matri = general_interaction_matrix(3) cores = general_make_H(inter_matri,single_terms) print("Matrix S:") display(cores[0]) print("Matrix M:") display(cores[1]) print("Matrix E:") display(cores[2]) ``` -------------------------------- ### Calculating Product of MPO Factors with SymPy (5 Atoms) Source: https://github.com/pasqal-io/emulators/blob/main/docs/developer/ising_MPO.ipynb This Python code calculates the product of the MPO factors previously generated for the 5-atom example. This product should result in the symbolic representation of the 5-atom Rydberg Hamiltonian. ```python mpo_factors_product(*cores) ``` -------------------------------- ### Aggregating Simulation Results (Custom Set) - emu-mps Python Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/noise.ipynb This example demonstrates using a custom aggregation function (`set_density`) to see the distribution of values for qubit density across Monte Carlo runs. The `set_density` function collects all unique density values observed for each qubit at each time step, illustrating the discrete nature of single-run outcomes. ```python def set_density(qubit_density_values: list[list[float]]): return [set(qubit_density[qubit_index] for qubit_density in qubit_density_values) for qubit_index in range(2)] aggregated_set_qubit_density = emu_mps.aggregate(results, occupation=set_density) aggregated_median_qubit_density.get_result(magnetization, 100/seq.get_duration())[0] # The extra 0.999 value comes from floating-point maths, Monte-Carlo logic and state renormalization. ``` -------------------------------- ### Calculating Product of MPO Factors with SymPy (3 Atoms) Source: https://github.com/pasqal-io/emulators/blob/main/docs/developer/ising_MPO.ipynb This Python code calculates the product of the MPO factors previously generated for the 3-atom example. This product should result in the symbolic representation of the 3-atom Rydberg Hamiltonian. ```python mpo_factors_product(*cores) ``` -------------------------------- ### Defining Initial State - emu-mps Python Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/noise.ipynb An initial state for the simulation is defined as an `emu_mps.MPS` object. It is initialized from state amplitudes, specifically setting the amplitude for the 'rr' (Rydberg-Rydberg) state to 1, indicating that the simulation starts with both qubits in the Rydberg state. ```python #define initial state initial_state = emu_mps.MPS.from_state_amplitudes(amplitudes={"rr":1.},eigenstates=basis) ``` -------------------------------- ### Building Documentation with mkdocs Source: https://github.com/pasqal-io/emulators/blob/main/docs/CONTRIBUTING.md Use this command to build the project documentation using the mkdocs static site generator. The `--clean` flag removes existing builds, and `--strict` causes the build to fail on warnings, ensuring documentation quality. ```shell mkdocs build --clean --strict ``` -------------------------------- ### Configuring and Initializing Backend - emu-mps Python Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/noise.ipynb This code creates an `emu_mps.MPSConfig` object, specifying the noise model, using CPU instead of GPU for small systems, setting an interaction cutoff to disable interactions, providing the initial state and observables, and setting the logging level. Finally, an `emu_mps.MPSBackend` is created, linking the Pulser sequence with the MPS configuration. ```python #define config and backend config = emu_mps.MPSConfig( noise_model=noise, num_gpus_to_use=0, #small systems are faster on cpu interaction_cutoff=1e10, #this will put all interactions to 0, regardless of spacing initial_state=initial_state, observables=[magnetization], log_level = logging.WARN #don't print stuff for the many runs ) backend = emu_mps.MPSBackend(seq, config=config) ``` -------------------------------- ### Checking Out and Pushing a New Git Branch Source: https://github.com/pasqal-io/emulators/blob/main/docs/CONTRIBUTING.md These commands check out the newly created local branch and then push it to the 'origin' remote repository. The `--set-upstream` flag associates the local branch with the remote one for easier pushing and pulling later. ```shell git checkout / git push --set-upstream origin / ``` -------------------------------- ### Running State Vector Emulation Python Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_sv/notebooks/getting_started.ipynb Imports the `SVBackend` class, which is the state vector emulator for Pulser sequences. It initializes the backend with the previously defined `seq` (the pulse sequence) and `config` (simulation configuration). The simulation is then executed by calling the `run()` method, and the resulting data is stored in the `results` variable. ```python from emu_sv import SVBackend bknd = SVBackend(seq, config=config) results = bknd.run() ``` -------------------------------- ### Importing Libraries for Pulser Register and Optimization in Python Source: https://github.com/pasqal-io/emulators/blob/main/examples/emu_mps/optimatrix/dumbbell.ipynb Imports required libraries: `pulser` for quantum register management, `numpy` for numerical operations, a local `helpers` module for utility functions, and `emu_mps.optimatrix` for optimization routines like minimizing bandwidth. ```python import pulser import numpy as np import helpers as helpers import emu_mps.optimatrix as optimatrix ``` -------------------------------- ### Importing Libraries Python Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_sv/notebooks/getting_started.ipynb Imports essential libraries for numerical operations (NumPy), plotting (Matplotlib), and quantum sequence definition/simulation (Pulser). These imports are required at the beginning of any script using these functionalities. ```python import numpy as np import matplotlib.pyplot as plt import pulser ``` -------------------------------- ### Configuring Simulation Observables Python Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_sv/notebooks/getting_started.ipynb Imports classes from the `emu_sv` module needed to configure the state vector simulation. It defines the simulation time step (dt), calculates the specific times at which the state should be evaluated, specifies the `Occupation` and `StateResult` observables to track during the simulation, and creates an `SVConfig` object containing these settings. ```python from emu_sv import StateResult, SVConfig, Occupation dt = 10 # timestep in ns seq_duration = seq.get_duration() eval_times = [t/seq_duration for t in range(dt, seq_duration, dt)] final_time = eval_times[-1] density = Occupation(evaluation_times=eval_times) state = StateResult(evaluation_times=[final_time]) # get the state at final time config = SVConfig(dt=dt, observables = [density, state], log_level=2000) ``` -------------------------------- ### Importing Simulation and Optimization Libraries (Python) Source: https://github.com/pasqal-io/emulators/blob/main/examples/emu_mps/optimatrix/periodic_1d_system.ipynb This snippet imports essential Python libraries required for the simulation and optimization tasks. It includes libraries for handling quantum registers (Pulser), numerical operations (NumPy), tensor manipulation (PyTorch), and custom helper/optimization modules. ```python import pulser import numpy as np import torch import helpers as helpers import emu_mps.optimatrix as optimatrix ``` -------------------------------- ### Defining Register and Parameters Python Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_sv/notebooks/getting_started.ipynb Sets physical parameters for the simulation, including system size, maximum Rabi frequency, detuning range, and pulse durations. It then calculates atomic coordinates for a ring configuration based on the Rydberg blockade radius and creates a Pulser Register object representing this geometry, finally drawing the register to visualize interactions. ```python # Setup L = 10 Omega_max = 2.3 * 2 * np.pi U = Omega_max / 2.3 delta_0 = -3 * U delta_f = 1 * U t_rise = 2000 t_fall = 2000 t_sweep = (delta_f - delta_0) / (2 * np.pi * 10) * 5000 # Define a ring of atoms distanced by a blockade radius distance: R_interatomic = pulser.MockDevice.rydberg_blockade_radius(U) coords = ( R_interatomic / (2 * np.sin(np.pi / L)) * np.array( [ (np.cos(theta * 2 * np.pi / L), np.sin(theta * 2 * np.pi / L)) for theta in range(L) ] ) ) # ring, periodic register reg = pulser.Register.from_coordinates(coords, prefix="q") # or try open boundaries #reg = pulser.Register.rectangle(1,L,spacing=R_interatomic/1.2, prefix="q") reg.draw(blockade_radius=R_interatomic, draw_half_radius=True, draw_graph=True) ``` -------------------------------- ### Defining Quench Pulser Sequence (Python) Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/benchmarks/index.md This Python snippet sets up a Pulser sequence for a quantum quench benchmark. It calculates pulse parameters based on an effective Ising model mapping and applies a single, constant-amplitude, constant-detuning pulse to a global Rydberg channel on a rectangular register, simulating a sudden turn-on of the driving field. It requires the Pulser library and NumPy. ```python hx = 1.5 # hx/J_max hz = 0 # hz/J_max t = 1.5 # t/J_max # Set up Pulser simulations R = 7 # μm reg = Register.rectangle(nx, ny, R, prefix="q") # Conversion from Rydberg Hamiltonian to Ising model U = AnalogDevice.interaction_coeff / R**6 # U_ij NN_coeff = U / 4 omega = 2 * hx * NN_coeff delta = -2 * hz * NN_coeff + 2 * U T = np.round(1000 * t / NN_coeff) seq = Sequence(reg, MockDevice) #circumvent the register spacing constraints seq.declare_channel("ising", "rydberg_global") # Add the main pulse to the pulse sequence simple_pulse = Pulse.ConstantPulse(T, omega, delta, 0) seq.add(simple_pulse, "ising") ``` -------------------------------- ### Importing Optimization Libraries - Python Source: https://github.com/pasqal-io/emulators/blob/main/examples/emu_mps/optimatrix/2d_grid.ipynb Imports necessary Python libraries including Pulser for quantum registers, NumPy for numerical operations, and custom 'helpers' and 'optimatrix' modules which provide utilities for lattice manipulation and optimization algorithms, respectively. These imports are prerequisites for defining and optimizing the qubit configuration. ```python import pulser import numpy as np import helpers as helpers import emu_mps.optimatrix as optimatrix ``` -------------------------------- ### Sampling and Plotting Final State Counts Python Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_sv/notebooks/getting_started.ipynb Shows how to sample measurement outcomes (bitstrings) from the final state using the `sample()` method. It samples 1000 times, filters outcomes that occurred more than 5 times, and generates a bar plot visualizing the frequency of these dominant bitstrings to identify the most probable observed configurations. ```python last_elem = -1 final_state = results.state[last_elem] counts = final_state.sample(num_shots=1000) large_counts = {k: v for k, v in counts.items() if v > 5} plt.figure(figsize=(15, 4)) plt.xticks(rotation=90, fontsize=14) plt.title("Most frequent observations") plt.bar(large_counts.keys(), large_counts.values()) ``` -------------------------------- ### Creating a New Feature Branch in Git Source: https://github.com/pasqal-io/emulators/blob/main/docs/CONTRIBUTING.md Use this command to create a new local branch in your Git repository. It's recommended to prefix the branch name with your initials for identification. ```shell git branch / ``` -------------------------------- ### Creating and Permuting Interaction Matrices (PyTorch, Optimatrix, Python) Source: https://github.com/pasqal-io/emulators/blob/main/examples/emu_mps/optimatrix/periodic_1d_system.ipynb Constructs a generic test matrix representing nearest-neighbor periodic interactions using PyTorch. It re-uses a previously found optimal permutation and demonstrates how to apply this permutation to a PyTorch tensor using an `optimatrix` utility function, illustrating the effect of reordering on the matrix representation. ```python m = 5 upper = torch.diag(torch.ones(m - 1), diagonal=1) lower = torch.diag(torch.ones(m - 1), diagonal=-1) initial_mat = upper + lower initial_mat[m - 1, 0] = initial_mat[0, m - 1] = 1 optimal_permutation = optimatrix.minimize_bandwidth(interactions) print(optimatrix.permute_tensor(interactions, optimal_permutation)) ``` -------------------------------- ### Creating Quench Sequence (Pulser, Python) Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_sv/benchmarks/index.md This snippet defines a Pulser sequence representing a quantum quench. It sets parameters analogous to an Ising model (hx, hz, t), calculates Rabi frequency (omega) and detuning (delta) based on interatomic distance (R) and conversion coefficients, and determines the pulse duration (T). It creates a rectangular register, defines a single constant pulse, declares a global rydberg channel, and adds the pulse to the sequence. It depends on the Pulser library and NumPy. ```python hx = 1.5 # hx/J_max hz = 0 # hz/J_max t = 1.5 # t/J_max # Set up Pulser simulations R = 7 # μm reg = Register.rectangle(nx, ny, R, prefix="q") # Conversion from Rydberg Hamiltonian to Ising model U = AnalogDevice.interaction_coeff / R**6 # U_ij NN_coeff = U / 4 omega = 2 * hx * NN_coeff delta = -2 * hz * NN_coeff + 2 * U T = np.round(1000 * t / NN_coeff) seq = Sequence(reg, MockDevice) #circumvent the register spacing constraints seq.declare_channel("ising", "rydberg_global") # Add the main pulse to the pulse sequence simple_pulse = Pulse.ConstantPulse(T, omega, delta, 0) seq.add(simple_pulse, "ising") ``` -------------------------------- ### Optimizing Qubit Ordering using Bandwidth Minimization in Python Source: https://github.com/pasqal-io/emulators/blob/main/examples/emu_mps/optimatrix/dumbbell.ipynb Calculates the reciprocal distance matrix representing qubit interactions using a `helpers` function. It then uses `optimatrix.minimize_bandwidth` to find an optimal permutation of qubits based on this interaction matrix. Finally, it applies this permutation to the `chain` register using `helpers.permute_sequence_registers` and visualizes the resulting reordered register. Requires `helpers`, `emu_mps.optimatrix`, and an initialized Pulser `Register`. ```python interactions = helpers.reciprocal_dist_matrix(chain) optimal_permutation = optimatrix.minimize_bandwidth(interactions, 100) chain = helpers.permute_sequence_registers(chain, optimal_permutation) chain.draw() ``` -------------------------------- ### Optimizing Initial Register Permutation - Python Source: https://github.com/pasqal-io/emulators/blob/main/examples/emu_mps/optimatrix/2d_grid.ipynb Calculates the interaction matrix based on qubit distances using `helpers.reciprocal_dist_matrix`. It then applies `optimatrix.minimize_bandwidth` to find an optimal permutation of qubits to minimize matrix bandwidth. This optimal permutation is applied to the register using `helpers.permute_sequence_registers`, and the resulting configuration is visualized. ```python interactions = helpers.reciprocal_dist_matrix(chain) optimal_permutation = optimatrix.minimize_bandwidth(interactions) print("optimal permutation is\n", optimal_permutation) chain = helpers.permute_sequence_registers(chain, optimal_permutation) chain.draw() ``` -------------------------------- ### Defining Adiabatic Pulser Sequence (Python) Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/benchmarks/index.md This Python snippet constructs a Pulser sequence for an adiabatic evolution benchmark. It defines parameters for pulse shapes (amplitude and detuning ramps) and applies them sequentially to a global Rydberg channel on a specified atomic register, simulating a slow, controlled change in the driving field. It requires the Pulser library and NumPy. ```python # from https://pulser.readthedocs.io/en/stable/tutorials/afm_prep.html # parameters in rad/µs and ns Omega_max = 2.0 * 2 * np.pi U = Omega_max / 2.0 delta_0 = -6 * U delta_f = 2 * U t_rise = 500 t_fall = 1000 t_sweep = (delta_f - delta_0) / (2 * np.pi * 10) * 3000 R_interatomic = MockDevice.rydberg_blockade_radius(U) reg = Register.rectangle(rows, columns, R_interatomic, prefix="q") if perm_map: reg_coords = reg._coords reg = Register.from_coordinates([reg_coords[i] for i in perm_map]) rise = Pulse.ConstantDetuning(RampWaveform(t_rise, 0.0, Omega_max), delta_0, 0.0) sweep = Pulse.ConstantAmplitude(Omega_max, RampWaveform(t_sweep, delta_0, delta_f), 0.0) fall = Pulse.ConstantDetuning(RampWaveform(t_fall, Omega_max, 0.0), delta_f, 0.0) seq = Sequence(reg, MockDevice) seq.declare_channel("ising", "rydberg_global") seq.add(rise, "ising") seq.add(sweep, "ising") seq.add(fall, "ising") ``` -------------------------------- ### Accessing Final State Vector Python Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_sv/notebooks/getting_started.ipynb Demonstrates how to retrieve the calculated state vector for the final time step from the `results` object produced by the simulation. It accesses the `state` attribute and then the `vector` of the last evaluated state, printing the first 10 elements to inspect the numerical representation of the quantum state. ```python last_elem = -1 results.state[last_elem].vector[:10] ``` -------------------------------- ### Running Multiple Monte Carlo Simulations - emu-mps Python Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/noise.ipynb This snippet performs multiple simulation runs (`nruns`) using the configured `emu_mps.MPSBackend`. It iterates a specified number of times, calling the `backend.run()` method in each iteration to execute a single Monte Carlo trajectory and stores the resulting `Results` objects in a list for later aggregation. ```python results = [] nruns = 500 #0.125 seconds per run on my machine for _ in range(nruns): results.append(backend.run()) ``` -------------------------------- ### Defining Pulser Sequence Python Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_sv/notebooks/getting_started.ipynb Constructs the sequence of pulses to be applied globally to the atomic register. It defines three distinct Pulses (rise, sweep, fall) using ramp waveforms for amplitude or detuning, declares a 'rydberg_global' channel, adds the pulses sequentially to a Pulser Sequence object, and then draws the sequence to visualize the applied fields. ```python rise = pulser.Pulse.ConstantDetuning( pulser.RampWaveform(t_rise, 0.0, Omega_max), delta_0, 0.0 ) sweep = pulser.Pulse.ConstantAmplitude( Omega_max, pulser.RampWaveform(t_sweep, delta_0, delta_f), 0.0 ) fall = pulser.Pulse.ConstantDetuning( pulser.RampWaveform(t_fall, Omega_max, 0.0), delta_f, 0.0 ) seq = pulser.Sequence(reg, pulser.MockDevice) seq.declare_channel("global", "rydberg_global") seq.add(rise, "global") seq.add(sweep, "global") seq.add(fall, "global") seq.draw() ``` -------------------------------- ### Plotting Simulation and Expected Results - Matplotlib Python Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/noise.ipynb Finally, this code imports `matplotlib.pyplot` and generates a plot comparing the theoretical expected magnetization decay to the mean magnetization obtained from the Monte Carlo simulation for different numbers of runs (`n`). This visualization shows how increasing the number of runs leads to convergence towards the expected theoretical value. ```python import matplotlib.pyplot as pl pl.plot(times, expected, label="expected") for n in [100, 200, 500]: pl.plot(times, densities(results, n), label=f"n = {n}") pl.legend(loc="upper right") ``` -------------------------------- ### Creating and Shuffling Periodic 1D Chain Registers (Pulser, Python) Source: https://github.com/pasqal-io/emulators/blob/main/examples/emu_mps/optimatrix/periodic_1d_system.ipynb Defines a function to generate coordinates for a periodic 1D chain mapped onto a circle and creates a Pulser register from these coordinates. It then demonstrates creating an original chain and a shuffled version using a helper utility, setting up the initial qubit layout for analysis. ```python def one_dim_systems_coords(L: int) -> np.ndarray: coords = ( np.array( [ (np.cos(theta * 2 * np.pi / L), np.sin(theta * 2 * np.pi / L)) for theta in range(L) ] ) ) reg = pulser.Register.from_coordinates(coords, prefix=None) return reg original_chain = one_dim_systems_coords(10) chain = helpers.shuffle_qubits(original_chain) ``` -------------------------------- ### Analyzing Interactions and Optimizing Qubit Order (Optimatrix, Python) Source: https://github.com/pasqal-io/emulators/blob/main/examples/emu_mps/optimatrix/periodic_1d_system.ipynb Calculates the interaction matrix based on the spatial distances between qubits in the current register configuration. It then applies an optimization algorithm from the `optimatrix` module to find and apply a permutation that minimizes the bandwidth of this interaction matrix, aiming for a more efficient representation or simulation. ```python interactions = helpers.reciprocal_dist_matrix(chain) print(interactions) optimal_permutation = optimatrix.minimize_bandwidth(interactions) print("optimal permutation is\n", optimal_permutation) chain = helpers.permute_sequence_registers(chain, optimal_permutation) chain.draw() ``` -------------------------------- ### Plotting Occupation Time Evolution Python Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_sv/notebooks/getting_started.ipynb Extracts the expectation value of the occupation number () for each atom across all evaluated time steps from the `results` object. It processes this data into a suitable format and generates two plots: a line plot showing vs. time for each qubit and a pseudocolor plot visualizing the occupation landscape over time and qubit index. ```python times = np.array(eval_times)*seq.get_duration() # array_of_occup = [np.array(results.occupation[i]) for i, t in enumerate(times)] occup = np.stack(array_of_occup, axis=1) fig, axs = plt.subplots(1,2, figsize=(8,4)) for i in range(L): axs[0].plot(times[0:], occup[i,:]) axs[0].set_xlabel("time [ns]") axs[0].set_ylabel(r"$\langle n_i\rangle$") pc = axs[1].pcolor(times, range(L), occup) axs[1].set_xlabel("time [ns]") axs[1].set_ylabel("qubit") fig.colorbar(pc, ax=axs[1], label = r"$\langle Z_i\rangle$") ``` -------------------------------- ### Drawing Pulser Register in Python Source: https://github.com/pasqal-io/emulators/blob/main/examples/emu_mps/optimatrix/dumbbell.ipynb Visualizes the current state of the Pulser `chain` register. This method uses Matplotlib internally to render the qubit positions based on the coordinates stored in the register object. Requires the `pulser` library and an initialized Pulser `Register` object. ```python chain.draw() ``` -------------------------------- ### Drawing Initial Pulser Register - Python Source: https://github.com/pasqal-io/emulators/blob/main/examples/emu_mps/optimatrix/2d_grid.ipynb Calls the `draw()` method on the `chain` Pulser Register object created in the previous step. This visualizes the spatial arrangement of the qubits (atoms) in the initial 5x5 lattice configuration before any optimization is applied. ```python chain.draw() ``` -------------------------------- ### Visualizing Qubit Registers (Pulser, Python) Source: https://github.com/pasqal-io/emulators/blob/main/examples/emu_mps/optimatrix/periodic_1d_system.ipynb Uses the built-in drawing method of the Pulser Register object to visually represent the spatial arrangement of qubits. This helps in understanding the initial layout and the effect of subsequent permutations on the qubit positions. ```python original_chain.draw() chain.draw() ``` -------------------------------- ### Defining Quantum Register - Pulser Python Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/noise.ipynb This code defines a quantum register using Pulser's `Register.from_coordinates`, placing two qubits at specified (x, y) coordinates. It then visualizes the register, setting the blockade radius to a small value and drawing the graph, which is relevant for systems with interactions (though interactions are later masked). ```python reg = pulser.Register.from_coordinates([[0,0],[10,0]]) reg.draw(blockade_radius=1e-10, draw_graph=True, draw_half_radius=True) #draw blockade radius as 0, since we will mask interactions in the MPSConfig ``` -------------------------------- ### Optimizing Register After Shuffling - Python Source: https://github.com/pasqal-io/emulators/blob/main/examples/emu_mps/optimatrix/2d_grid.ipynb Performs a second optimization pass. It first shuffles the qubits in the `chain` register randomly using `helpers.shuffle_qubits`. It then repeats the optimization steps: calculates interactions, finds a new optimal permutation for the shuffled layout using `optimatrix.minimize_bandwidth`, applies it with `helpers.permute_sequence_registers`, and visualizes the final configuration. ```python # Second optimisation with shuffling chain = helpers.shuffle_qubits(chain) interactions = helpers.reciprocal_dist_matrix(chain) optimal_permutation = optimatrix.minimize_bandwidth(interactions) print("optimal permutation is\n", optimal_permutation) chain = helpers.permute_sequence_registers(chain, optimal_permutation) chain.draw() ``` -------------------------------- ### Aggregating Simulation Results (Mean) - emu-mps Python Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/noise.ipynb This demonstrates how to use the `emu_mps.aggregate` static method to combine results from multiple simulation runs. By default, it computes the mean for observables like qubit density. The commented lines show how the default aggregation type can be inspected and how to retrieve an aggregated result at a specific time. ```python #this is temporarily not available pending a move of the aggregation functionality to pulser #this is currently under discussion #magnetization.default_aggregation_type #this api will be subject to change when the aggregation functionality moves to pulser. aggregated_results = emu_mps.aggregate(results) aggregated_results.get_result(magnetization, 100/seq.get_duration())[0] # average magnetization of qubit 0 at time 100ns ``` -------------------------------- ### Defining Utility Function for Plotting - Python Source: https://github.com/pasqal-io/emulators/blob/main/docs/emu_mps/notebooks/noise.ipynb A helper function `densities` is defined to simplify accessing and formatting the aggregated qubit density results for plotting. It takes a list of results and the number of runs to include, aggregates them, and extracts the density values for the first qubit. ```python def densities(results, n): return [density[0] for density in emu_mps.aggregate(results[:n]).occupation] ``` -------------------------------- ### Creating 2D Pulser Register - Python Source: https://github.com/pasqal-io/emulators/blob/main/examples/emu_mps/optimatrix/2d_grid.ipynb Defines the `two_dim_lattice` function to generate a 2D grid of coordinates and convert them into a `pulser.Register` object. The function takes grid dimensions M and N as input. An instance of a 5x5 lattice is then created and assigned to the `chain` variable, centered. ```python def two_dim_lattice(M: int, N: int) -> np.ndarray: coords = np.array( [ (i, j) for j in range(M) for i in range(N) ] ) def rotate(theta, vector): rotation = np.array([ [np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)] ]) return rotation @ vector rotated_coords = np.array([rotate(0, coord) for coord in coords]) return pulser.Register.from_coordinates(rotated_coords, center = True) chain = two_dim_lattice(5, 5) ``` -------------------------------- ### Testing 5-Atom MPO against Reference Hamiltonian with SymPy Source: https://github.com/pasqal-io/emulators/blob/main/docs/developer/ising_MPO.ipynb This Python code compares the string representation of the product of the 5-atom MPO factors with the string representation of the reference Rydberg Hamiltonian for 5 qubits. The comparison `==` is expected to evaluate to `True` if the MPO construction is correct. ```python str(reference_hamiltonian(5)) == str(mpo_factors_product(*cores)) ``` -------------------------------- ### Testing 3-Atom MPO against Reference Hamiltonian with SymPy Source: https://github.com/pasqal-io/emulators/blob/main/docs/developer/ising_MPO.ipynb This Python code compares the string representation of the product of the 3-atom MPO factors with the string representation of the reference Rydberg Hamiltonian for 3 qubits. The comparison `==` is expected to evaluate to `True` if the MPO construction is correct. ```python str(reference_hamiltonian(3)) == str(mpo_factors_product(*cores)) ``` -------------------------------- ### Creating Reference Rydberg Hamiltonian with SymPy Source: https://github.com/pasqal-io/emulators/blob/main/docs/developer/ising_MPO.ipynb This Python function generates a direct symbolic representation of the Rydberg Hamiltonian for a given number of qubits. It is used as a reference to verify that the MPO construction correctly reproduces the full Hamiltonian. ```python def reference_hamiltonian(qubit_count: int): """For testing puruposes: creates the Rydberg Hamiltonian """ result = sum(gate_at({i: A(i)}, qubit_count) for i in range(qubit_count)) result += sum( U(j, i) * gate_at({ i: n_op(i), j: n_op(j) }, qubit_count) for i in range(qubit_count) for j in range(i) ) return result ```