The post Calculating resource estimates for cryptanalysis appeared first on Q# Blog.
]]>This blog offers an inside look into the computation of these estimates. Our resource estimator supports various input formats for quantum programs, including Q# and Qiskit, which are then translated into QIR, the Quantum Intermediate Representation. In addition to customizable qubit parameters, we also utilize predefined models in our experience. To perform resource estimation of physical hardware components from logical resource counts (which do not take the overhead for quantum error correction into account) extracted from papers, we utilize a specialized resource estimation operation in Q#. Furthermore, we have developed an algorithm in Rust and translated it into QIR by leveraging the LLVM framework, which also powers QIR. The following three sections delve into the specific details for each encryption algorithm addressed in our interactive experience.
In the experience we compare the following three cryptographic algorithms in different key strengths (for elliptic curve cryptography, these correspond to concrete prime field Weierstrass curves, which you can lookup via the link):
Algorithm | Standard | Enhanced | Highest |
---|---|---|---|
Elliptic curve | P-256 | P-384 | P-521 |
RSA | 2048 | 3072 | 4096 |
AES | 128 | 192 | 256 |
In the estimation, we assume that we lower the quantum algorithm to a sequence of physical quantum gates. For these we assume the following two choices of qubits and error rates. The values are based on some pre-defined qubit parameters available in the resource estimator. The Majorana and gate-based pre-defined parameters in the resource estimator correspond to topological and superconducting qubit types in the experience, respectively.
Qubit type and error rate | Majorana (reasonable) | Majorana (optimistic) | Gate-based (reasonable) | Gate-based (optimistic) |
---|---|---|---|---|
Measurement time | 100 ns | 100 ns | 100 ns | 100 ns |
Gate time | 100 ns | 100 ns | 50 ns | 50 ns |
Measurement error rate | 0.0001 | 0.000001 | 0.001 | 0.0001 |
Gate error rate | 0.05 | 0.01 | 0.001 | 0.0001 |
Elliptic curve cryptography (ECC) is a public-key cryptography approach based on the algebraic structure of elliptic curves. The approach requires smaller key sizes compared to approaches such as RSA, while providing an equal security against classical cryptanalysis methods. The paper Improved quantum circuits for elliptic curve discrete logarithms (arXiv:2001.09580) describes a quantum algorithm to solve the elliptic curve discrete logarithm problem (ECDLP) based on Shor’s algorithm. We make use of the Q# operation AccountForEstimates
(also find details on how to use the operation) that allows us to derive physical resource estimates from previously computed logical ones. This operation is very helpful when logical estimates have already been computed, as for example in this paper and listed in there as part of Table 1.
From that table we extract the relevant metrics, which are the number of T gates, the number of measurement operations, and the number of qubits. The other metrics are not relevant for the computation, since the physical resource estimation relies on Parallel Synthesis Sequential Pauli Computation (PSSPC, Appendix D in arXiv:2211.07629), which commutes all Clifford operations and replaces them by multi-qubit Pauli measurements. The paper discusses various optimization flags in the implementation to minimize the logical qubit count, T count, or the logical depth. We found that the physical resource estimates are best, both for physical qubits and runtime, when using the option to minimize qubit count. The following Q# program includes the estimates for the considered key sizes 256, 384, and 521.
open Microsoft.Quantum.ResourceEstimation;
operation ECCEstimates(keysize: Int) : Unit {
if keysize == 256 {
use qubits = Qubit[2124];
AccountForEstimates([
TCount(7387343750), // 1.72 * 2.0^32
MeasurementCount(118111601) // 1.76 * 2.0^26
], PSSPCLayout(), qubits);
} else if keysize == 384 {
use qubits = Qubit[3151];
AccountForEstimates([
TCount(25941602468), // 1.51 * 2.0^34
MeasurementCount(660351222) // 1.23 * 2.0^29
], PSSPCLayout(), qubits);
} else if keysize == 521 {
use qubits = Qubit[4258];
AccountForEstimates([
TCount(62534723830), // 1.82 * 2.0^35
MeasurementCount(1707249501) // 1.59 * 2.0^30
], PSSPCLayout(), qubits);
} else {
fail $"keysize {keysize} is not supported";
}
}
We can estimate this Q# program by submitting it to an Azure Quantum workspace using the azure_quantum Python package. To do so, we are setting up a connection to an Azure Quantum workspace (Learn how to create a workspace). You can find the values for resource_id and location in the Overview page of the Quantum workspace. (The complete code example is available on GitHub)
workspace = Workspace(
resource_id="",
location=""
)
estimator = MicrosoftEstimator(workspace)
We then define the input parameters for the job. In there we specify the key size, here 256. We use batching to submit multiple target parameter configurations at once. In here we specify the four configurations that correspond to the realistic and optimistic settings for both gate-based and Majorana qubits. For all configurations, we set the error budget to 0.333, i.e., we compute physical resource estimates considering a success rate about 67%.
params = estimator.make_params(num_items=4)
params.arguments["keysize"] = 256
# Error budget
params.error_budget = 0.333
# Gate-based (realistic)
params.items[0].qubit_params.name = QubitParams.GATE_NS_E3
# Gate-based (optimistic)
params.items[1].qubit_params.name = QubitParams.GATE_NS_E4
# Majorana (realistic)
params.items[2].qubit_params.name = QubitParams.MAJ_NS_E4
params.items[2].qec_scheme.name = QECScheme.FLOQUET_CODE
# Majorana (optimistic)
params.items[3].qubit_params.name = QubitParams.MAJ_NS_E6
params.items[3].qec_scheme.name = QECScheme.FLOQUET_CODE
Finally, we create a job by submitting the Q# operation together with the input parameters, and retrieve the results after it has completed. We then use the result object to create a summary table using the summary_data_frame
function. The table contains various entries, but in this example, we only print the numbers of physical qubits and physical runtimes, the same that are plotted in the experience on the Azure Quantum website.
job = estimator.submit(ECCEstimates, input_params=params)
results = job.get_results()
table = results.summary_data_frame(labels=[
"Gate-based (reasonable)",
"Gate-based (optimistic)",
"Majorana (reasonable)",
"Majorana (optimistic)"
])
print()
print(table[["Physical qubits", "Physical runtime"]])
The output is as follows:
Physical qubits Physical runtime
Gate-based (reasonable) 5.87M 21 hours
Gate-based (optimistic) 1.54M 11 hours
Majorana (reasonable) 3.69M 8 hours
Majorana (optimistic) 1.10M 4 hours
The estimates in the table are formatted for better readability. You can also retrieve the non-formatted values, e.g., the number of physical qubits and physical items for the first configuration (gate-based realistic) are access with results[0]["physicalCounts"]["physicalQubits"]
and results[0]["physicalCounts"]["runtime"]
, respectively.
RSA is one of the oldest, yet widely used, public-key cryptography approaches. The paper How to factor 2048 bit RSA integers in 8 hours using 20 million noisy qubits (arXiv:1905.09749) describes an implementation to factor RSA integers based on state-of-the-art quantum operations for phase estimation and quantum arithmetic. The code is mostly similar to the code we are using for ECC estimates described above. However, we implemented the algorithm in Rust, and compiled it to LLVM. Therefore, we submit the QIR, which is the LLVM output, directly to the Azure Quantum Resource Estimator. (The complete code example is available on GitHub)
import urllib.request
bitcode = urllib.request.urlopen("https://aka.ms/RE/some-nice-uri").read()
The entry point in this implementation takes 4 input arguments, the actual product (in this sample the 2048-bit RSA integer from the RSA challenge), a generator, and two parameters to control windowed arithmetic in the implementation. We take its values from the paper, in which 5 is suggested a good value for both of them. Then, we configure the qubit parameters and QEC scheme as above in the input parameters, and submit them together with the bitcode to the resource estimator.
params = estimator.make_params(num_items=4)
params.arguments["product"] = "25195908475657893494027183240048398571429282126204032027777137836043662020707595556264018525880784406918290641249515082189298559149176184502808489120072844992687392807287776735971418347270261896375014971824691165077613379859095700097330459748808428401797429100642458691817195118746121515172654632282216869987549182422433637259085141865462043576798423387184774447920739934236584823824281198163815010674810451660377306056201619676256133844143603833904414952634432190114657544454178424020924616515723350778707749817125772467962926386356373289912154831438167899885040445364023527381951378636564391212010397122822120720357"
params.arguments["generator"] = 7
params.arguments["exp_window_len"] = 5
params.arguments["mul_window_len"] = 5
# specify error budget, qubit parameter and QEC scheme assumptions
params.error_budget = 0.333
# ...
job = estimator.submit(bitcode, input_params=params)
results = job.get_results()
The code for evaluating the data is the same and returns the following table:
Physical qubits Physical runtime
Gate-based (reasonable) 25.17M 1 days
Gate-based (optimistic) 5.83M 12 hours
Majorana (reasonable) 13.40M 9 hours
Majorana (optimistic) 4.18M 5 hours
We can use the same program to compute resource estimates for other RSA integers, including the RSA challenge numbers RSA-3072 and RSA-4096, whose estimates are part of the cryptography experience on the Azure Quantum website.
The Advanced Encryption Standard (AES) is a symmetric-key algorithm and a standard for the US federal government. In order to obtain the physical resource estimates for breaking AES, we started from the logical estimates in Implementing Grover oracles for quantum key search on AES and LowMC (arXiv:1910.01700, Table 8), with updates on the qubit counts suggested in Quantum Analysis of AES (Cryptology ePrint Archive, Paper 2022/683, Table 7). In principle, we can follow the approach using the AccountForEstimates
function as we did for ECC. This operation and the logical counts in the Azure Quantum Resource Estimator are represented using 64-bit integers for performance reasons, however, for the AES estimates we need 256-bit integers. As a result we used an internal non-production version of the resource estimator that can handle this precision. Further details can be made available to researchers if you run into similar precision issues in your resource estimation projects.
The Azure Quantum Resource Estimator can be applied to estimate any quantum algorithm, not only cryptanalysis. Learn how to get started in Azure Quantum today with the Azure Quantum documentation. In there you find how to explore all the rich capabilities in various notebooks, with applications in quantum chemistry, quantum simulation, and arithmetic. You can learn how to submit your own quantum programs written in Q#, Qiskit, or directly provided as QIR, as well as how to set up advanced resource estimation experiments and apply customizations such as space/time tradeoffs.
The post Calculating resource estimates for cryptanalysis appeared first on Q# Blog.
]]>The post Automate Resource Estimation with QIR appeared first on Q# Blog.
]]>In these samples, e.g., Estimation with Q# input, you can generate resource estimations like the following that are computed from a quantum program that multiplies two quantum registers (click on the titles to unfold more tables with details on the estimates):
Physical qubits | 8160 |
Number of physical qubits
This value represents the total number of physical qubits, which is the sum of 4200 physical qubits to implement the algorithm logic, and 3960 physical qubits to execute the T factories that are responsible to produce the T states that are consumed by the algorithm. |
Runtime | 6ms 80us |
Total runtime
This is a runtime estimate (in nanosecond precision) for the execution time of the algorithm. In general, the execution time corresponds to the duration of one logical cycle (10us) multiplied by the 608 logical cycles to run the algorithm. If however the duration of a single T factory (here: 82us 500ns) is larger than the algorithm runtime, we extend the number of logical cycles artificially in order to exceed the runtime of a single T factory. |
Logical algorithmic qubits | 84 |
Number of logical qubits for the algorithm after layout
Laying out the logical qubits in the presence of nearest-neighbor constraints requires additional logical qubits. In particular, to layout the ${Q}_{\mathrm{a}\mathrm{l}\mathrm{g}}=33$ logical qubits in the input algorithm, we require in total $2\cdot {Q}_{\mathrm{a}\mathrm{l}\mathrm{g}}+\lceil \sqrt{8\cdot {Q}_{\mathrm{a}\mathrm{l}\mathrm{g}}}\rceil +1=84$ logical qubits. |
Algorithmic depth | 608 |
Number of logical cycles for the algorithm
To execute the algorithm using Parallel Synthesis Sequential Pauli Computation (PSSPC), operations are scheduled in terms of multi-qubit Pauli measurements, for which assume an execution time of one logical cycle. Based on the input algorithm, we require one multi-qubit measurement for the 8 single-qubit measurements, the 0 arbitrary single-qubit rotations, and the 0 T gates, three multi-qubit measurements for each of the 192 CCZ and 8 CCiX gates in the input program, as well as No rotations in algorithm multi-qubit measurements for each of the 0 non-Clifford layers in which there is at least one single-qubit rotation with an arbitrary angle rotation. |
Logical depth | 608 |
Number of logical cycles performed
This number is usually equal to the logical depth of the algorithm, which is 608. However, in the case in which a single T factory is slower than the execution time of the algorithm, we adjust the logical cycle depth to exceed the T factory’s execution time. |
Number of T states | 800 |
Number of T states consumed by the algorithm
To execute the algorithm, we require one T state for each of the 0 T gates, four T states for each of the 192 CCZ and 8 CCiX gates, as well as No rotations in algorithm for each of the 0 single-qubit rotation gates with arbitrary angle rotation. |
Number of T factories | 11 |
Number of T factories capable of producing the demanded 800 T states during the algorithm’s runtime
The total number of T factories 11 that are executed in parallel is computed as $\lceil {\displaystyle \frac{800\phantom{\rule{thickmathspace}{0ex}}\text{T states}\cdot 82us500ns\phantom{\rule{thickmathspace}{0ex}}\text{T factory duration}}{1\phantom{\rule{thickmathspace}{0ex}}\text{T states per T factory}\cdot 6ms80us\phantom{\rule{thickmathspace}{0ex}}\text{algorithm runtime}}}\rceil $ |
Number of T factory invocations | 73 |
Number of times all T factories are invoked
In order to prepare the 800 T states, the 11 copies of the T factory are repeatedly invoked 73 times. |
Physical algorithmic qubits | 4200 |
Number of physical qubits for the algorithm after layout
The 4200 are the product of the 84 logical qubits after layout and the 50 physical qubits that encode a single logical qubit. |
Physical T factory qubits | 3960 |
Number of physical qubits for the T factories
Each T factory requires 360 physical qubits and we run 11 in parallel, therefore we need $3960=360\cdot 11$ qubits. |
Required logical qubit error rate | 9.79e-9 |
The minimum logical qubit error rate required to run the algorithm within the error budget
The minimum logical qubit error rate is obtained by dividing the logical error probability 5.00e-4 by the product of 84 logical qubits and the total cycle count 608. |
Required logical T state error rate | 6.25e-7 |
The minimum T state error rate required for distilled T states
The minimum T state error rate is obtained by dividing the T distillation error probability 5.00e-4 by the total number of T states 800. |
Number of T states per rotation | No rotations in algorithm |
Number of T states to implement a rotation with an arbitrary angle
The number of T states to implement a rotation with an arbitrary angle is $\lceil 0.53{\mathrm{log}}_{2}(0/0)+5.3\rceil $ [arXiv:2203.10064]. For simplicity, we use this formula for all single-qubit arbitrary angle rotations, and do not distinguish between best, worst, and average cases. |
QEC scheme | surface_code |
Name of QEC scheme
You can load pre-defined QEC schemes by using the name |
Code distance | 5 |
Required code distance for error correction
The code distance is the smallest odd integer greater or equal to $\frac{2\mathrm{log}(0.08/0.000000009790100250626566)}{\mathrm{log}(0.0015/0.000001)}}-1$ |
Physical qubits | 50 |
Number of physical qubits per logical qubit
The number of physical qubits per logical qubit are evaluated using the formula 2 * codeDistance * codeDistance that can be user-specified. |
Logical cycle time | 10us |
Duration of a logical cycle in nanoseconds
The runtime of one logical cycle in nanoseconds is evaluated using the formula 20 * oneQubitMeasurementTime * codeDistance that can be user-specified. |
Logical qubit error rate | 2.37e-11 |
Logical qubit error rate
The logical qubit error rate is computed as $0.08\cdot {\left({\displaystyle \frac{0.000001}{0.0015}}\right)}^{\frac{5+1}{2}}$ |
Crossing prefactor | 0.08 |
Crossing prefactor used in QEC scheme
The crossing prefactor is usually extracted numerically from simulations when fitting an exponential curve to model the relationship between logical and physical error rate. |
Error correction threshold | 0.0015 |
Error correction threshold used in QEC scheme
The error correction threshold is the physical error rate below which the error rate of the logical qubit is less than the error rate of the physical qubit that constitute it. This value is usually extracted numerically from simulations of the logical error rate. |
Logical cycle time formula | 20 * oneQubitMeasurementTime * codeDistance |
QEC scheme formula used to compute logical cycle time
This is the formula that is used to compute the logical cycle time 10us. |
Physical qubits formula | 2 * codeDistance * codeDistance |
QEC scheme formula used to compute number of physical qubits per logical qubit
This is the formula that is used to compute the number of physical qubits per logical qubits 50. |
Physical qubits | 360 |
Number of physical qubits for a single T factory
This corresponds to the maximum number of physical qubits over all rounds of T distillation units in a T factory. A round of distillation contains of multiple copies of distillation units to achieve the required success probability of producing a T state with the expected logical T state error rate. |
Runtime | 82us 500ns |
Runtime of a single T factory
The runtime of a single T factory is the accumulated runtime of executing each round in a T factory. |
Number of output T states per run | 1 |
Number of output T states produced in a single run of T factory
The T factory takes as input 345 noisy physical T states with an error rate of 0.01 and produces 1 T states with an error rate of 2.52e-7. |
Number of input T states per run | 345 |
Number of physical input T states consumed in a single run of a T factory
This value includes the physical input T states of all copies of the distillation unit in the first round. |
Distillation rounds | 2 |
The number of distillation rounds
This is the number of distillation rounds. In each round one or multiple copies of some distillation unit is executed. |
Distillation units per round | 23, 1 |
The number of units in each round of distillation
This is the number of copies for the distillation units per round. |
Distillation units | 15-to-1 space efficient physical, 15-to-1 space efficient logical |
The types of distillation units
These are the types of distillation units that are executed in each round. The units can be either physical or logical, depending on what type of qubit they are operating. Space-efficient units require fewer qubits for the cost of longer runtime compared to Reed-Muller preparation units. |
Distillation code distances | 1, 3 |
The code distance in each round of distillation
This is the code distance used for the units in each round. If the code distance is 1, then the distillation unit operates on physical qubits instead of error-corrected logical qubits. |
Number of physical qubits per round | 276, 360 |
The number of physical qubits used in each round of distillation
The maximum number of physical qubits over all rounds is the number of physical qubits for the T factory, since qubits are reused by different rounds. |
Runtime per round | 4us 500ns, 78us |
The runtime of each distillation round
The runtime of the T factory is the sum of the runtimes in all rounds. |
Logical T state error rate | 2.52e-7 |
Logical T state error rate
This is the logical T state error rate achieved by the T factory which is equal or smaller than the required error rate 6.25e-7. |
Logical qubits (pre-layout) | 33 |
Number of logical qubits in the input quantum program
We determine 84 from this number by assuming to align them in a 2D grid. Auxiliary qubits are added to allow for sufficient space to execute multi-qubit Pauli measurements on all or a subset of the logical qubits. |
T gates | 0 |
Number of T gates in the input quantum program
This includes all T gates and adjoint T gates, but not T gates used to implement rotation gates with arbitrary angle, CCZ gates, or CCiX gates. |
Rotation gates | 0 |
Number of rotation gates in the input quantum program
This is the number of all rotation gates. If an angle corresponds to a Pauli, Clifford, or T gate, it is not accounted for in this number. |
Rotation depth | 0 |
Depth of rotation gates in the input quantum program
This is the number of all non-Clifford layers that include at least one single-qubit rotation gate with an arbitrary angle. |
CCZ gates | 192 |
Number of CCZ-gates in the input quantum program
This is the number of CCZ gates. |
CCiX gates | 8 |
Number of CCiX-gates in the input quantum program
This is the number of CCiX gates, which applies $-iX$ controlled on two control qubits [1212.5069]. |
Measurement operations | 8 |
Number of single qubit measurements in the input quantum program
This is the number of single qubit measurements in Pauli basis that are used in the input program. Note that all measurements are counted, however, the measurement result is is determined randomly (with a fixed seed) to be 0 or 1 with a probability of 50%. |
Total error budget | 1.00e-3 |
Total error budget for the algorithm
The total error budget sets the overall allowed error for the algorithm, i.e., the number of times it is allowed to fail. Its value must be between 0 and 1 and the default value is 0.001, which corresponds to 0.1%, and means that the algorithm is allowed to fail once in 1000 executions. This parameter is highly application specific. For example, if one is running Shor’s algorithm for factoring integers, a large value for the error budget may be tolerated as one can check that the output are indeed the prime factors of the input. On the other hand, a much smaller error budget may be needed for an algorithm solving a problem with a solution which cannot be efficiently verified. This budget $\u03f5={\u03f5}_{\mathrm{log}}+{\u03f5}_{\mathrm{d}\mathrm{i}\mathrm{s}}+{\u03f5}_{\mathrm{s}\mathrm{y}\mathrm{n}}$ is uniformly distributed and applies to errors ${\u03f5}_{\mathrm{log}}$ to implement logical qubits, an error budget ${\u03f5}_{\mathrm{d}\mathrm{i}\mathrm{s}}$ to produce T states through distillation, and an error budget ${\u03f5}_{\mathrm{s}\mathrm{y}\mathrm{n}}$ to synthesize rotation gates with arbitrary angles. Note that for distillation and rotation synthesis, the respective error budgets ${\u03f5}_{\mathrm{d}\mathrm{i}\mathrm{s}}$ and ${\u03f5}_{\mathrm{s}\mathrm{y}\mathrm{n}}$ are uniformly distributed among all T states and all rotation gates, respectively. If there are no rotation gates in the input algorithm, the error budget is uniformly distributed to logical errors and T state errors. |
Logical error probability | 5.00e-4 |
Probability of at least one logical error
This is one third of the total error budget 1.00e-3 if the input algorithm contains rotation with gates with arbitrary angles, or one half of it, otherwise. |
T distillation error probability | 5.00e-4 |
Probability of at least one faulty T distillation
This is one third of the total error budget 1.00e-3 if the input algorithm contains rotation with gates with arbitrary angles, or one half of it, otherwise. |
Rotation synthesis error probability | 0.00e0 |
Probability of at least one failed rotation synthesis
This is one third of the total error budget 1.00e-3. |
Qubit name | qubit_maj_ns_e6 |
Some descriptive name for the qubit model
You can load pre-defined qubit parameters by using the names |
Instruction set | Majorana |
Underlying qubit technology (gate-based or Majorana)
When modeling the physical qubit abstractions, we distinguish between two different physical instruction sets that are used to operate the qubits. The physical instruction set can be either gate-based or Majorana. A gate-based instruction set provides single-qubit measurement, single-qubit gates (incl. T gates), and two-qubit gates. A Majorana instruction set provides a physical T gate, single-qubit measurement and two-qubit joint measurement operations. |
Single-qubit measurement time | 100 ns |
Operation time for single-qubit measurement (t_meas) in ns
This is the operation time in nanoseconds to perform a single-qubit measurement in the Pauli basis. |
Two-qubit measurement time | 100 ns |
Operation time for two-qubit measurement in ns
This is the operation time in nanoseconds to perform a non-destructive two-qubit joint Pauli measurement. |
T gate time | 100 ns |
Operation time for a T gate
This is the operation time in nanoseconds to execute a T gate. |
Single-qubit measurement error rate | 1E-06 |
Error rate for single-qubit measurement
This is the probability in which a single-qubit measurement in the Pauli basis may fail. |
Two-qubit measurement error rate | 1E-06 |
Error rate for two-qubit measurement
This is the probability in which a non-destructive two-qubit joint Pauli measurement may fail. |
T gate error rate | 0.01 |
Error rate to prepare single-qubit T state or apply a T gate (p_T)
This is the probability in which executing a single T gate may fail. |
More details on the following lists of assumptions can be found in the paper Accessing requirements for scaling quantum computers and their applications.
Uniform independent physical noise. We assume that the noise on physical qubits and physical qubit operations is the standard circuit noise model. In particular we assume error events at different space-time locations are independent and that error rates are uniform across the system in time and space.
Efficient classical computation. We assume that classical overhead (compilation, control, feedback, readout, decoding, etc.) does not dominate the overall cost of implementing the full quantum algorithm.
Extraction circuits for planar quantum ISA. We assume that stabilizer extraction circuits with similar depth and error correction performance to those for standard surface and Hastings-Haah code patches can be constructed to implement all operations of the planar quantum ISA (instruction set architecture).
Uniform independent logical noise. We assume that the error rate of a logical operation is approximately equal to its space-time volume (the number of tiles multiplied by the number of logical time steps) multiplied by the error rate of a logical qubit in a standard one-tile patch in one logical time step.
Negligible Clifford costs for synthesis. We assume that the space overhead for synthesis and space and time overhead for transport of magic states within magic state factories and to synthesis qubits are all negligible.
Smooth magic state consumption rate. We assume that the rate of T state consumption throughout the compiled algorithm is almost constant, or can be made almost constant without significantly increasing the number of logical time steps for the algorithm.
«We have found the Resource Estimator simple to use despite the complexity of fault-tolerant computing schemes. In this tool, resource estimates are neatly laid-out at a glance, and hardware parameters and model assumptions can be easily tweaked. This kind of tool represents exactly what will be needed for designing large-scale algorithms and computations that are more resource-efficient, which is a major focus of our work.», notes Jing Hao CHAI, Fault-tolerant Quantum Computing Scientist at Entropica Labs.
To use the Resource Estimator in your day-to-day quantum computing work, and especially to automate multiple resource estimation jobs, it may be more convenient to integrate it directly into your existing toolchains or workflows. In this blog post, we will we use the Azure Quantum Python library to write a Python tool that returns resource estimates in JSON format given as input a QIR program. The complete source code is available in this Gist.
With the tool it will be possible to generate physical resource estimates as simple as:
python estimate.py quantum_program.qir > resources.json
Let’s start by declaring some program arguments:
import argparse, os
parser = argparse.ArgumentParser(
prog="estimate",
description="Estimate physical resources using Azure Quantum")
parser.add_argument("filename", help="Quantum program (.ll, .qir, .bc)")
parser.add_argument("-r", "--resource-id",
default=os.environ.get("AZURE_QUANTUM_RESOURCE_ID"),
help="Resource ID of Azure Quantum workspace (must be set, unless set via \
environment variable AZURE_QUANTUM_RESOURCE_ID)")
parser.add_argument("-l", "--location",
default=os.environ.get("AZURE_QUANTUM_LOCATION)"),
help="Location of Azure Quantum workspace (must be set, unless set via \
environment AZURE_QUANTUM_LOCATION")
parser.add_argument("-p", "--job-params", help="JSON file with job parameters")
args = parser.parse_args()
The Python tool takes a QIR program as input, which can be either in human-readable LLVM code (.ll
and .qir
extension) or directly in bitcode (.bc
extension). The tool also requires information on how to connect with your Azure Quantum workspace, which can be either passed as program arguments or using environment variables. An Azure Quantum workspace allows us to access the resource estimator target and submit resource estimation jobs. If you do not have an Azure account, you can set one up for free here. If you do not have an Azure Quantum workspace, create one here. You will find the resource id and the location in the Overview page of your Azure Quantum workspace.
Next, we are connecting to our Azure Quantum workspace:
from azure.quantum import Workspace
workspace = Workspace(resource_id=args.resource_id, location=args.location)
The QIR program can be generated from other quantum programming languages like Q# (more details on generating QIR from Q#) and Qiskit (more details on generating QIR from Qiskit). If the QIR is provided in human-readable LLVM code, we first need to translate it to LLVM bitcode via the PyQIR library:
from pyqir.generator import ir_to_bitcode
ext = os.path.splitext(args.filename)[1].lower()
if ext in ['.qir', '.ll']:
with open(args.filename, 'r') as f:
bitcode = ir_to_bitcode(f.read())
elif ext == '.bc':
with open(args.filename, 'rb') as f:
bitcode = f.read()
In addition to the quantum program, one can pass job parameters that model the assumed fault-tolerant quantum computer. If no job parameters are specified, a default model is assumed. You can find more information on the job parameters in the Azure Quantum documentation. Job parameters can be provided through an optional JSON file such as the following:
{
"qubitParams": {"name": "qubit_maj_ns_e6"},
"qecScheme": {"name": "floquet_code"},
"errorBudget": 0.005
}
We read such a file using Python’s json
library:
import json
input_params = {}
if args.job_params:
with open(args.job_params, 'r') as f:
input_params = json.load(f)
Now that we have access to the Azure Quantum workspace, the QIR program, and the job parameters, we are ready to create and submit a resource estimation job:
from azure.quantum import Job
job = Job.from_input_data(
workspace=workspace,
name="Estimation job",
target="microsoft.estimator",
input_data=bitcode,
provider_id="microsoft-qc",
input_data_format="qir.v1",
output_data_format="microsoft.resource-estimates.v1",
input_params=input_params
)
job.wait_until_completed()
Now all what is left to do is to extract the results from the job and print them in JSON format:
result = job.get_results()
print(json.dumps(result, indent=4))
The resulting output looks as follows:
{
// ...
"physicalCounts": {
"breakdown": {
"algorithmicLogicalDepth": 3479912,
"algorithmicLogicalQubits": 217,
"cliffordErrorRate": 0.001,
"logicalDepth": 3479912,
"numTfactories": 18,
"numTfactoryRuns": 242135,
"numTsPerRotation": 15,
"numTstates": 4358430,
"physicalQubitsForAlgorithm": 191394,
"physicalQubitsForTfactories": 599760,
"requiredLogicalQubitErrorRate": 4.4141872274122407e-13,
"requiredLogicalTstateErrorRate": 7.648013925503755e-11
},
"physicalQubits": 791154,
"runtime": 29231260800
},
// ...
}
Here are some ideas on what to try next:
The post Automate Resource Estimation with QIR appeared first on Q# Blog.
]]>The post Testing large quantum algorithms using sparse simulation appeared first on Q# Blog.
]]>The sparse simulator (SparseSimulator
) makes use of the fact that, for many applications, the quantum state has relatively few nonzero amplitudes. Instead of storing these zeros, which is what the full state simulator (QuantumSimulator
) does, the sparse simulator only stores nonzero amplitudes together with an index identifying the corresponding computational basis state.
The main sources of sparsity in the quantum state are (1) structure in the underlying problem such as particle conservation in physical systems and (2) the use of auxiliary qubits in, e.g., arithmetic subroutines such as modular exponentiation in Shor’s algorithm for factoring. For (2), note that these auxiliary qubits don’t increase the number of nonzero amplitudes in the superposition, but just how many bits are in each computational basis state.
In addition to only storing nonzero amplitudes, the sparse simulator has a built-in mechanism to queue certain quantum gates to keep the state sparse for as long as possible. One example of quantum operations that are queued are Hadamard gates: These are only executed once it is no longer possible to easily commute incoming quantum operations through the queued Hadamard gates. As a simple example, consider two qubits with a Hadamard gate on each qubit, followed by a controlled NOT (CNOT) gate. Instead of directly applying the Hadamard gates (which could increase the number of nonzero amplitudes in the state vector), we can commute the CNOT through them, and apply a CNOT with flipped control and target qubit, keeping the two Hadamard gates in our queue of gates that are yet to be applied. The following circuit diagrams illustrate this mechanism:
Let’s see how the two simulators perform when simulating Shor’s algorithm for determining the prime factors p and q of N = p · q. The sample can be found in the Q# samples repository.
When using Toffoli-based arithmetic, the sparse simulator clearly outperforms the full-state simulator, which only managed to simulate the smallest instance within 1 minute. With the sparse simulator, on the other hand, we can even simulate factoring N=1443, which used 49 qubits!
Even when the state vector is not particularly sparse, the sparse simulator often outperforms the highly optimized and multithreaded full state simulator. One example is Shor’s algorithm with Fourier-based arithmetic. Compared to the Toffoli-based implementation, there are fewer auxiliary qubits in the implementation and the quantum Fourier transforms make the states denser, both of which reduce the runtime advantage of the sparse simulator. However, the advantage is still substantial at larger problem sizes even if it uses only a single core, whereas the full state simulator uses all four cores.
All of these benchmarks were run on a ThinkPad X1 laptop with four cores and we report the median runtime of dotnet run --no-build -- [application arguments]
out of three runs. For reproducibility, we manually set the generator to two in Shor.qs
(instead of selecting it at random).
We hope that the newly released sparse simulator helps you with testing and debugging large-scale quantum algorithms. We have found it to be most useful for chemistry algorithms and quantum algorithms that make use of oracles that rely on many auxiliary qubits to achieve reversibility and/or faster execution.
If you’re interested to learn more about sparse simulation, please check out the documentation for the sparse simulator, the paper Leveraging state sparsity for more efficient quantum simulations by Samuel Jaques and Thomas Häner, and two samples that use the sparse simulator for factoring and GHZ state preparation.
The post Testing large quantum algorithms using sparse simulation appeared first on Q# Blog.
]]>The post Visualizing resource estimates with the trace simulator and quantum-viz.js appeared first on Q# Blog.
]]>Here is an example of the circuit we’re going to get for Shor’s algorithm sample. Note how each operation is annotated with the resources it requires to run.
The high-level steps are as follows. (You can find the complete implementation in the integer factorization sample of the Quantum repository.)
Model the circuit schema used by quantum-viz.js as C# classes with Json.NET. This allows us to readily generate JSON from it.
Implement the IQCTraceSimulatorListener
interface for a custom listener that can be used with the trace simulator. It provides hooks whenever a primitive operation is executed, when an operation starts and ends, and when qubits are allocated.
public class QuantumVizCounter : IQCTraceSimulatorListener
Whenever qubits are allocated, we add them to the primary inputs of the circuit. Note that all auxiliary qubits are also added as primary inputs to the circuit due to way the circuit model is defined in quantum-viz.js, but they are going to be re-used when possible.
public void OnAllocate(object[] qubitsTraceData) { foreach (var qubit in qubitsTraceData) { Circuit.Qubits.Add(new Qubit { /* ... */ }); } }
When a new operation starts, push a new operation instance to a call stack. We’re also keeping track of whether this operation is adjoint, because quantum-viz.js has a dedicated visualization for adjoint execution operations.
public void OnOperationStart(HashedString name, OperationFunctor variant, object[] qubitsTraceData) { var operation = new Operation { Gate = name }; foreach (var qubit in qubitsTraceData) { operation.Targets.Add((QubitReference)qubit); } operation.IsAdjoint = variant == OperationFunctor.Adjoint || variant == OperationFunctor.ControlledAdjoint; callStack.Push(operation); }
When a primitive operation is executed, the counter of the operation instance at the top of the stack are updated.
public void OnPrimitiveOperation(int id, object[] qubitsTraceData, double duration) { switch (id) { case (int)PrimitiveOperationsGroups.CNOT: callStack.Peek().CNOTCount += 1.0; break; // ... } }
When an operation ends, the most current operation instance is popped from the stack and added as a child to the new operation instance at the top of the stack (its parent operation).
public void OnOperationEnd(object[] returnedQubitsTraceData) { var lastOperation = callStack.Pop(); callStack.Peek().AddChild(lastOperation); }
We add this listener to a custom resource estimator, which then can be invoked on any Q# operation. After execution, the circuit object can be serialized to JSON.
var estimator = new QuantumVizEstimator(); MyOperation.Run(estimator).Wait(); var json = JsonConvert.SerializeObject(estimator.Circuit);
Feel free to run the sample, generate the JSON representation of the circuit with resources annotations, and display it using quantum-viz.js. You can readily extend the existing example to use the generated JSON data (use JSON.parse(json)
to turn the JSON output into a JavaScript object).
Here are some suggestions for possible steps, especially considering free time during the holidays .
Use special visualizations for primitive gates such as H
or CNOT
.
Provide a command line argument to specify the call depth.
Instead of outputting the JSON data, one could generate an HTML file that contains all the code to display the circuit.
Color the operations based on the resource counts similar to the flame graph visualization. This requires changes to the quantum-viz.js library to modify the appearance of operations.
Extract the control qubits of a controlled operation. This requires non-trivial changes to the trace simulator (This file is a good starting point. One would need to extract the control qubits from inputValue
and pass them separately in the listener’s call to OnOperationStart
. The latter requires a change in the interface.).
We are welcoming pull requests to either the samples repository or quantum-viz.js repository, if you are interested in implementing some of these or other suggestions in the existing sample.
Happy holidays!
The post Visualizing resource estimates with the trace simulator and quantum-viz.js appeared first on Q# Blog.
]]>The post The AutoSubstitution rewrite step appeared first on Q# Blog.
]]>Microsoft.Quantum.AutoSubstitution
which provides a rewrite step that allows you to substitute some Q# operations with alternatives dynamically depending on which simulator you use to execute them. This feature has been requested by users through feedback in Github.
This is best explained with a small example. Assume you have an implementation that swaps two qubits, in which all the CNOT
operations have the target on the same qubit. Such an operation can be useful in an architecture that constrains the direction in which CNOT
operations can be executed. This can be achieved in the following way:
operation ApplySingleDirectionSWAP(a : Qubit, b : Qubit) : Unit is Adj + Ctl { within { CNOT(a, b); H(a); H(b); } apply { CNOT(a, b); } }
The operation is functionally equivalent to Microsoft.Quantum.Intrinsic.SWAP
, however, the Toffoli simulator cannot execute it due to the non-classical Hadamard operation, whereas it can execute the SWAP
operation. That means that we also cannot execute other classical operations (e.g., arithmetic operations) with the Toffoli simulator, if they make use of the ApplySingleDirectionSWAP
operation.
In these situations, Microsoft.Quantum.AutoSubstitution
can help by allowing us to instruct the Q# compiler to use the original SWAP operation as an alternative for our custom ApplySingleDirectionSWAP
operation, whenever we execute the program with the Toffoli simulator. We simply need to add this information using an annotation:
open Microsoft.Quantum.Targeting; @SubstitutableOnTarget("Microsoft.Quantum.Intrinsic.SWAP", "ToffoliSimulator") operation ApplySingleDirectionSWAP(a : Qubit, b : Qubit) : Unit is Adj + Ctl { // ... }
You need to add the dependency to Microsoft.Quantum.AutoSubstitution
into your project file as a Q# reference
<PackageReference Include="Microsoft.Quantum.AutoSubstitution" Version="0.18.2106148911" IsQscReference="true" />
and open the Microsoft.Quantum.Targeting
, which contains the SubstitutableOnTarget
attribute. Note that for now, you must ensure that the alternative operation is referenced somewhere in the code. To be sure, you could add
let _ = Microsoft.Quantum.Intrinsic.SWAP;
as the first line of your operation ApplySingleDirectionSWAP
. (This limitation will be resolved in our next release.) You can find a sample project that uses the new rewrite step in our samples repository.
Of course, this attribute is not limited to Toffoli simulator, but can be used with the other simulators from the QDK or using custom simulators. (Make sure to address custom simulators with their fully classified names.) Replacing operations with alternatives is also referred to as emulation. The new rewrite step makes this scenario much more accessible.
The package makes use of custom compilation steps, a technique that you can use to extend the compiler. If you are interested in the underlying techniques that support this new NuGet package, you find more information on extending the Q# compiler in this blog post.
The post The AutoSubstitution rewrite step appeared first on Q# Blog.
]]>The post Emulation in Q# appeared first on Q# Blog.
]]>The first technique employs intrinsic functions. It can be used to implement a Q# function using a .NET language, for example C#. This is particularly useful when the required library functionality is not available in Q#. Let’s consider the following example, typical for this season. We often find ourselves in need of the number of the remaining days until Christmas as a value in our Q# operation. This value can be used as the secret shift in the hidden shift algorithm or as the special element in a Grover search. We cannot retrieve the current date in Q#, and therefore we have to provide this function as an intrinsic:
function DaysUntilChristmas() : Int { body intrinsic; }
This Q# code gets translated into a partial C# class called DaysUntilChristmas
.
We can add the implementation by creating a nested class called Native
that
derives from DaysUntilChristmas
. In its constructor this class accepts an instance m
of type IOperationFactory
, which is the base class to
all simulators. Therefore, m
holds the simulator object for which the
function is called. This will enable us to perform simulator-dependent code.
We’ll use this variable when describing the second technique, however, in this
example we compute the same function independent of the simulator.
public partial class DaysUntilChristmas { public class Native : DaysUntilChristmas { public Native(IOperationFactory m) : base(m) {} // can be adjusted (e.g., 1/7, 1/6, 1/19, ...) private static int ChristmasMonth => 12; private static int ChristmasDay => 25; // C# function with matching signature private long DaysUntilChristmasFunction(QVoid input) { var today = DateTime.Today; var christmas = new DateTime(today.Year, ChristmasMonth, ChristmasDay); // make sure the next Christmas is today or in the future if (christmas < today) christmas = christmas.AddYears(1); // return difference in days return (christmas - today).Days; } // Override __Body__ property to use C# function public override Func__Body__ => DaysUntilChristmasFunction; } }
The function implementation is provided in terms of the __Body__
property,
which is of type Func<__In__, __Out__>
, where __In__
is the type of the input
argument tuple in the Q# function and __Out__
is the type of the output
argument tuple. In this case, there are no input arguments,
represented by the empty tuple type QVoid
, and there is a single output
argument of Q# type Int
, which corresponds to the C# type long
(= Int64
).
We can use a similar technique to replace an existing function with an
alternative one. It would be nice to make Q#’s Message
outputs a little more
festive this month. For this purpose, we implement an intrinsic function called
ChristmasMessage
:
function ChristmasMessage(msg : String) : Unit { body intrinsic; }
The implementation is provided in C# and it prefixes each message with Santa’s way of expressing his happiness and joy.
public partial class ChristmasMessage { public class Native : ChristmasMessage { private readonly IOperationFactory simulator; public Native(IOperationFactory m) : base(m) { simulator = m; } private QVoid ChristmasMessageFunction(string message) { if (simulator is ToffoliSimulator) { WriteLine(message); } else { WriteLine($"Ho ho ho: {message}"); } return QVoid.Instance; } public override Func__Body__ => ChristmasMessageFunction; } }
Note that we do not override the behavior when using the ToffoliSimulator
,
since the Toffoli simulator does not support the H
in Ho ho ho.
Instead of replacing all occurrences of Message
in our Q# programs by hand, we
wish to do this automatically and hide the existence of the ChristmasMessage
from our users. When constructing a simulator instance in a C# host program, we
can use the Register
method to replace an existing function with an
alternative one.
var simulator = new QuantumSimulator(); simulator.Register(typeof(Message), typeof(ChristmasMessage), typeof(ICallable));
The first parameter is the type of the existing function, in this case
Message
, the second parameter is the type of the replacement, in this case
ChristmasMessage
, and the third parameter is the type of either ICallable
,
IAdjointable
, IControllable
, or IUnitary
. For functions and operations
that are neither adjoint nor controlled, use ICallable
. For operations that
are marked Adj
but not Ctl
, use IAdjointable
, for those marked Ctl
but
not Adj
, use IControllable
, and if they have both, use IUnitary
.
It’s worth to note that we couldn’t just have implemented ChristmasMessage
by
calling Message
inside and then use Register
for the replacement. This
would have lead to an infinite recursive loop, since the Message
call inside
ChristmasMessage
would also be replaced.
The same technique can be used to replace one Q# operation by another Q# operation, the replacement operation doesn’t need to be implemented in C#. Consider the following operation that swaps two qubits using three CNOTs:
operation SwapWithCNOTs(a : Qubit, b : Qubit) is Adj+Ctl { CNOT(a, b); CNOT(b, a); CNOT(a, b); }
To replace the SWAP
operation in the Microsoft.Quantum.Intrinsic
namespace,
we can simply call Register
on the simulator like this:
simulator.Register(typeof(SWAP), typeof(SwapWithCNOTs), typeof(IUnitary));
The emulation technique was demonstrated for Q# functions in this post, but it
applies similarly to Q# operations as well. However, depending on the supported
functors (such as Adj
and Ctl
), operations for __AdjointBody__
,
__ControlledBody__
, and __AdjointControlledBody__
might also need to be implemented.
The technique is used in several places in the Q# and QDK code base. Some examples include:
Microsoft.Quantum.Math
namespace.ApplyAnd
and ApplyLowDepthAnd
with CCNOT
when used with the Toffoli simulator.The complete code for the project can be found in this gist.
It’s DaysUntilChristmas()
days until Christmas, ChristmasMessage("Happy
holidays!")
.
The post Emulation in Q# appeared first on Q# Blog.
]]>The post Build your own Q# simulator – Part 3: A circuit-diagram builder with ⟨q|pic⟩ appeared first on Q# Blog.
]]>Assume we wish to create a circuit diagram for the following Q# operation, which implements T-state injection, using a custom simulator.
internal operation TInject(a : Qubit, b : Qubit) : Unit { H(a); T(a); Barrier(); CNOT(b, a); ApplyIfOne(MResetZ(a), (S, b)); }
While running the Q# program, the simulator picks up every operation in the execution trace and writes a line into a file following the ⟨q|pic⟩ file format to describe circuit diagrams:
q0 W q1 W q0 H q0 G {$T$} BARRIER q1 +q0 q0 M q1 G {$S$} q0 q0 X q0 OUT {}
This file can then be converted into a PNG picture (or alternatively TeX code, or a PDF):
Note that the S
operation on the second qubit is conditionally applied based on the measurement outcome of the first qubit.
In order to generate the file and specify the filename we make use of Q#’s testing framework, in which the custom simulator can be explicitly specified (this makes Q# test projects a convenient place to draw circuit diagrams):
@Test("Microsoft.Quantum.Samples.QpicSimulator") operation PrintTInject() : Unit { within { SavePicture("t-injection.qpic"); } apply { using ((a, b) = (Qubit(), Qubit())) { TInject(a, b); } } }
The operation Barrier
in the TInject
operation and the operation SavePicture
in the test operation are not part of the Q# libraries and are custom operations that we provide with the custom simulator. Their Q# implementation is empty, so it’s safe to use them with other simulators, e.g., the default full-state simulator.
namespace Microsoft.Quantum.Samples { operation SavePicture(filename : String) : Unit is Adj {} operation Barrier() : Unit {} }
We will override their behavior when they are used with the custom QpicSimulator
. This blog post focuses on their implementation. The accompanying sample also illustrates how to handle the ApplyIfOne
operation using the IQuantumProcessor
interface.
As seen in the previous posts (part 1 and part 2), we implement the custom simulator using two classes, QpicProcessor
based on QuantumProcessorBase
and QpicSimulator
based on QuantumProcessorDispatcher
. The processor class keeps track of open picture scopes using a C# dictionary, called Pictures
, which maps filenames to StringBuilder
objects. These objects are initially empty. Executing intrinsic operations will add text to all open StringBuilder
objects using the public AddCommand
method:
namespace Microsoft.Quantum.Samples { class QpicSimulatorProcessor : QuantumProcessorBase { public Dictionary<String, StringBuilder> Pictures { get; } = new Dictionary<String, StringBuilder>(); public void AddCommand(string format, params object[] args) { foreach (var builder in Pictures.Values) { builder.AppendFormat(QpicFormatter.Instance, format, args); builder.AppendLine(); } } public override void OnAllocateQubits(IQArray<Qubit> qubits) { foreach (var qubit in qubits) { AddCommand("{0} W", qubit); } } public override void X(Qubit qubit) { AddCommand("{0} X", qubit); } public override void H(Qubit qubit) { AddCommand("{0} H", qubit); } public override void S(Qubit qubit) { AddCommand("{0} G {{$S$}}", qubit); } // more operations... } }
The AppendFormat
call inside AddCommand
makes use of a custom formatter QpicFormatter
, which has custom formatting instructions for Qubit
and Pauli
objects and therefore allows us to pass qubit objects directly as formatting arguments. We next discuss how to implement the SavePicture
operation for the simulator to insert and remove items into the Pictures
dictionary.
As can you see in the example, the custom simulator makes use of the operation SavePicture
to dump the executed operations into a file. We use the default behavior of SavePicture
to start tracking operations and its Adjoint
functor to stop tracking:
SavePicture("filename.qpic"); // operations to be tracked Adjoint SavePicture("filename.qpic");
Because of this structure we can make use of the apply / within
conjugation
statement, which further only requires to specify the filename once:
within { SavePicture("filename.qpic"); } apply { // operations to be tracked }
The simulator class QpicSimulator
is initialized with a new QpicProcessor
instance. Simulator-specific implementations of operations are implemented as part of the simulator class using nested classes.
namespace Microsoft.Quantum.Samples { public class QpicSimulator : QuantumProcessorDispatcher { public QpicSimulator() : base(new QpicProcessor()) {} // simulator-specific implementation of SavePicture public class SavePictureImpl : SavePicture { // ... } // simulator-specific implementation of Barrier public class BarrierImpl : Barrier { // ... } } }
We first discuss the implementation of SavePicture
.
// custom implementation for: // // Name In-type Out-type // ┌────┴────┐┌────────┴────────┐ ┌┴─┐ // operation SavePicture(filename : String) : Unit {} // public class SavePictureImpl : SavePicture { // └────┬────┘ // Name private QpicProcessor processor; public SavePictureImpl(QpicSimulator m) : base(m) { processor = (QpicProcessor)m.QuantumProcessor; } // Getter property for Q# operation body // // In-type Out-type // ┌─┴──┐ ┌─┴─┐ public override Func<String, QVoid> Body => // Arguments // ┌──┴───┐ filename => { // ┐ processor.Pictures.Add(filename, new StringBuilder()); // ├ Lambda of type Func<String, QVoid> return QVoid.Instance; // │ }; // ┘ // Getter property for Q# operation adjoint body public override Func<String, QVoid> AdjointBody => filename => { System.IO.File.WriteAllText(filename, processor.Pictures[filename].ToString()); processor.Pictures.Remove(filename); return QVoid.Instance; }; }
The comments should help to understand how the name, input type, and output type of the Q# operation are used in the implementation as C# class. The Q# String
type corresponds to the C# String
type, and the Q# Unit
type corresponds to the C# QVoid
type, which is contained in the Microsoft.Quantum.Simulation.Core
namespace. The class must have a constructor that takes as input a QpicSimulator
object. We make use of the constructor to obtain a reference to the QpicProcessor
instance through which we can access the Pictures
dictionary and the AddCommand
method (used in the implementation for Barrier
). Each specialization, in this case body
and adjoint body
, are represented in terms of a getter property in the class, which return a Func<InType, OutType>
object corresponding to the operation signature in Q#. If the Q# operation has no arguments, InType
is QVoid
, and if the Q# operation has more than one argument, InType
is a value tuple type over all input types in the Q# operation. We construct the Func<String, QVoid>
object using a lambda expression. For the Body
, a new empty StringBuilder
object is inserted into the document having the filename as its key. For the AdjointBody
, the StringBuilder
object is looked up in the dictionary, and its collected text is written to the corresponding filename. Afterwards, the entry is removed from the dictionary.
The implementation of the Barrier
is class is much simpler, as it merely adds one ⟨q|pic⟩ command:
public class BarrierImpl : Barrier { private QpicProcessor processor; public BarrierImpl(QpicSimulator m) : base(m) { processor = (QpicProcessor)m.QuantumProcessor; } public override Func<QVoid, QVoid> Body => _ => { processor.AddCommand("BARRIER"); return QVoid.Instance; }; }
The underscore _
is used as argument name for the input argument, which is of type QVoid
, since the Q# Barrier
operation has no input arguments.
We hope that this blog post helped highlight some alternative applications for custom simulators and how to implement them using custom simulator-based intrinsic operations. We are eager to hear your questions or suggestions, and hope to address all of them in upcoming posts in this series!
The post Build your own Q# simulator – Part 3: A circuit-diagram builder with ⟨q|pic⟩ appeared first on Q# Blog.
]]>The post Build your own Q# simulator – Part 2: Advanced features for the reversible simulator appeared first on Q# Blog.
]]>IQuantumProcessor
interface to write a custom simulator that performs reversible simulation in a quantum circuit that is composed solely using classical reversible operations.
This blog post builds up on the first part of this series to show how to implement more advanced simulator capabilities:
ApplyAnd
in the reversible simulator, which have a classical simulation behavior but are implemented using non-classical quantum operations.AssertQubit
.BitArray
instead of a dictionary.Recall the ApplyMajority
Q# operation from the previous blog post. It uses a CCNOT
operation whose target is the qubit that will hold the output value for the majority operation. In typical applications it’s safe to assume that the target qubit is in a zero state. Therefore, we can replace the CCNOT
operation with the ApplyAnd
operation. Using this operation has the advantage that it requires less resources in a fault-tolerant quantum computing implementation, and therefore yields more accurate resource estimates. To make sure that the ApplyMajority
operation is invoked as expected, we further add a call to AssertQubit
.
operation ApplyMajority(a : Qubit, b : Qubit, c : Qubit, f : Qubit) : Unit { AssertQubit(Zero, f); within { CNOT(b, a); CNOT(b, c); } apply { ApplyAnd(a, c, f); CNOT(b, f); } }
The current implementation of the reversible simulator is not suitable to simulate this operation due to two problems:
AssertQubit
will not raise an error when qubit f
is in state One
, because asserting qubit states is not yet implemented in the custom simulator.ApplyAnd
will cause a runtime exception, because its library implementation contains non-classical operations such as H
and T
.We address the first problem by overriding and implementing the Assert
method in ReversibleSimulatorProcessor
as follows:
public override void Assert(IQArray<Qubit> bases, IQArray<Qubit> qubits, Result expected, string msg) { if (bases.Count == 1 && bases[0] == Pauli.PauliZ) { if (GetValue(qubits[0]).ToResult() != expected) { throw new ExecutionFailException(msg); } } else { throw new InvalidOperationException("Assert only supported for single-qubit Z-measurements"); } }
In this implementation we only consider the case of a single-qubit measurement in the Z-basis. In this case we check whether the current state of the asserted qubit corresponds to the expected value. Since GetValue
returns the qubit’s state as a bool
, we translate it to a Result
type using the ToResult()
method.
The second problem is addressed by simply treating the ApplyAnd
function as the CCNOT
function, since they behave equally when simulated classically. You can setup this mapping in the constructor of the ReversibleSimulator
class:
public ReversibleSimulator(bool throwOnReleasingQubitsNotInZeroState = true) : base(new ReversibleSimulatorProcessor { ThrowOnReleasingQubitsNotInZeroState = throwOnReleasingQubitsNotInZeroState } { // Perform ApplyAnd as it was CCNOT Register(typeof(ApplyAnd), typeof(CCNOT), typeof(IUnitary<(Qubit, Qubit, Qubit)>)); }
The type that is passed as the third argument must match the type of both operations, and can be derived from the operation’s Q# signature. For most operations, the type is IUnitary
specified over a value tuple type with respect to the signature in Q#, which is 3 Qubit
arguments in the case of ApplyAnd
and CCNOT
.
Also note that the constructor of the ReversibleSimulator
contains a parameter throwOnReleasingQubitsNotInZeroState
that mimics the behavior in QuantumSimulator
. Its use in the OnReleaseQubits
is discussed later in the blog post.
With the QDK it’s possible to directly write the host program in Q# and specify the simulator used to run it via the project file (or command line). For this purpose, it is advised to develop the Q# host program and the C# simulator in two separate projects, let’s say host.csproj
for the Q# program and simulator.csproj
for the C# library containing the custom Q# simulator. This will also allow you to reuse the simulator among several different Q# projects. The Q# project only contains Q# source files with one file including the entry point operation:
@EntryPoint() operation RunMajority(a : Bool, b : Bool, c : Bool) : Bool { using ((qa, qb, qc, f) = (Qubit(), Qubit(), Qubit(), Qubit())) { within { ApplyPauliFromBitString(PauliX, true, [a, b, c], [qa, qb, qc]); } apply { ApplyMajority(qa, qb, qc, f); } return IsResultOne(MResetZ(f)); } }
This entry point operation takes as input 3 Boolean values for the simulation of the majority function, and it returns a Boolean value indicating the result.
The simulator is specified in the Q# project file as follows:
<Project Sdk="Microsoft.Quantum.Sdk/0.11.2004.2825"> <PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> <OutputType>Exe</OutputType> <DefaultSimulator>Microsoft.Quantum.Samples.ReversibleSimulator</DefaultSimulator> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\simulator\simulator.csproj" /> </ItemGroup> </Project>
The DefaultSimulator
tag contains the class name (including the complete namespace) for the custom simulator that should be used to run the Q# program, and the ProjectReference
contains a reference to the simulator project file (which could also be a NuGet package reference, for example).
If you are using the command line, you can run the Q# program and pass it some example simulation values that will be passed as parameters to the @EntryPoint
operation (in our case RunMajority
):
dotnet run -- -a true -b true -c true
which will output True
to the terminal. You can also override the default simulator for the project (specify a different simulator as simulation target) using the -s
option in the call to dotnet run
.
The implementation for the reversible simulator in the first blog post was using IDictionary<Qubit, bool>
as a container to store the simulation values, which is simple to use but not very efficient in runtime and memory usage. In this implementation we are using a BitArray
instead, which stores multiple Boolean values in a single integer. We can use the qubit’s id to index this array.
private BitArray simulationValues = new BitArray(64);
We pre-initialize the array to 64 bits, and grow it dynamically whenever qubits are allocated which exceed the size of the array:
public override void OnAllocateQubits(IQArray<Qubit> qubits) { // returns the largest qubit's id in qubits var maxId = qubits.Max(q => q.Id); // double the bit array's size as a dynamic growing strategy; // newly allocated bits are initialized to false while (maxId >= simulationValues.Length) { simulationValues.Length *= 2; } }
The following three helper methods are added to the ReversibleSimulatorProcessor
to facilitate the access and modification of the simulation values:
private bool GetValue(Qubit qubit) { return simulationValues[qubit.Id]; } private void SetValue(Qubit qubit, bool value) { simulationValues[qubit.Id] = value; } private void InvertValue(Qubit qubit) { simulationValues[qubit.Id] = !simulationValues[qubit.Id]; }
These methods are, e.g., used in the OnReleaseQubits
method, in which simulation values are restored to false
. The method also throws an assertion in case a qubit’s simulation value is true
when being released and the ThrowOnReleasingQubitsNotInZeroState
is set to true
.
public override void OnReleaseQubits(IQArray<Qubit> qubits) { foreach (var qubit in qubits) { if (GetValue(qubit)) { if (ThrowOnReleasingQubitsNotInZeroState) { throw new ReleasedQubitsAreNotInZeroState(); } SetValue(qubit, false); } } }
We hope that this blog post provided further insights in how to integrate some more advanced techniques when developing your own simulator. In the next blog post we will show you how to build a custom simulator that allows you to create a circuit diagram of your code. We are eager to hear your questions or suggestions, and hope to address all of them in upcoming posts in this series!
The post Build your own Q# simulator – Part 2: Advanced features for the reversible simulator appeared first on Q# Blog.
]]>The post Build your own Q# simulator – Part 1: A simple reversible simulator appeared first on Q# Blog.
]]>IQuantumProcessor
interface makes it very easy to write your own simulators and integrate them into your Q# projects. This blog post is the first in a series that covers this interface. We start by implementing a reversible simulator as a first example, which we extend in future blog posts. A reversible simulator can simulate quantum programs that consist only of classical operations: X
, CNOT
, CCNOT
(Toffoli gate), or arbitrarily controlled X
operations. Since a reversible simulator can represent the quantum state by assigning one Boolean value to each qubit, it can run even quantum programs that consist of thousands of qubits. This simulator is very useful for testing quantum operations that evaluate Boolean functions.
You start writing your own simulator by extending the QuantumProcessorBase
class:
class ReversibleSimulatorProcessor : QuantumProcessorBase { private IDictionary<Qubit, bool> simulationValues = new Dictionary<Qubit, bool>(); // define custom actions for intrinsic operations... }
A dictionary that will store the current simulation value for each qubit in the program has already been added to the class. The classical quantum states |0⟩ and |1⟩ are represented as the Boolean values false
and true
, respectively. Simulators define intrinsic operations in the Q# program. QuantumProcessorBase
contains a method for each intrinsic operation, which you can override to define its behavior in the new simulator. Note that unimplemented methods throw an exception by default. This will help you spot cases in which an operation still needs to be implemented and tells the user of the simulator that an intrinsic operation is not supported by the simulator. For example, a reversible simulator cannot be used to simulate quantum programs that contain non-classical operations such as H
or T
.
Let’s begin by initializing newly allocated qubits to false
by overriding the OnAllocateQubits
method. Similarly, qubits are removed from the dictionary when they are released. The OnReleaseQubits
is called in that case.
public override void OnAllocateQubits(IQArray<Qubit> qubits) { foreach (var qubit in qubits) { simulationValues[qubit] = false; } } public override void OnReleaseQubits(IQArray<Qubit> qubits) { foreach (var qubit in qubits) { simulationValues.Remove(qubit); } }
With these two operations, it is guaranteed that the simulation values are available whenever an operation is applied to some qubit, and that no simulation values remain in the dictionary for qubits which are not present in the current scope.
As a next step, let’s implement the actions of classical operations. Two methods suffice in this case, the X
method is called when an X
operation is simulated, and the ControlledX
method is called when an arbitrarily controlled X
operation is simulated, which also includes CNOT
and CCNOT
. The action of an X
operation inverts the simulation value of a qubit, whereas in the case of an arbitrarily controlled X
operation the target qubit is inverted if and only if all control qubits are assigned true
.
public override void X(Qubit qubit) { simulationValues[qubit] = !simulationValues[qubit]; } public override void ControlledX(IQArray<Qubit> controls, Qubit qubit) { simulationValues[qubit] ^= And(controls); }
Finally, measuring a qubit in a Q# program returns a result (One
or Zero
), which can be computed based on the qubit’s current simulation value. We also implement the Reset
method that is called when the Reset
operation is called in a Q# program. The latter does not remove the qubit from the current scope, but resets the simulation value back to its initial value, which is false
.
public override Result M(Qubit qubit) { return simulationValues[qubit] ? Result.One : Result.Zero; } public override void Reset(Qubit qubit) { simulationValues[qubit] = false; }
The ReversibleSimulatorProcessor
can be used as a simulator by constructing a QuantumProcessorDispatcher
instance. A recommended design style is to provide a dedicated class for the simulator:
public class ReversibleSimulator : QuantumProcessorDispatcher { public ReversibleSimulator() : base(new ReversibleSimulatorProcessor()) {} }
Let’s try the new simulator on the following Q# operation, which implements a majority-of-three voter:
operation ApplyMajority(a : Qubit, b : Qubit, c : Qubit, f : Qubit) : Unit { within { CNOT(b, a); CNOT(b, c); } apply { CCNOT(a, c, f); CNOT(b, f); } }
We also write an operation that exercises the majority operation by providing three Boolean input values:
operation RunMajority(a : Bool, b : Bool, c : Bool) : Bool { using ((qa, qb, qc, f) = (Qubit(), Qubit(), Qubit(), Qubit())) { within { ApplyPauliFromBitString(PauliX, true, [a, b, c], [qa, qb, qc]); } apply { ApplyMajority(qa, qb, qc, f); } return MResetZ(f) == One; } }
Finally, you can put everything together by calling the Q# operation using the new simulator in a C# host program. The following example evaluates the majority operation for all different input assignments and prints all simulation results:
public static void Main(string[] args) { var sim = new ReversibleSimulator(); var bits = new[] {false, true}; foreach (var a in bits) { foreach (var b in bits) { foreach (var c in bits) { var f = RunMajority.Run(sim, a, b, c).Result; Console.WriteLine($"Majority({a,5}, {b,5}, {c,5}) = {f,5}"); } } } }
We hope that this blog post inspires you to try writing your own simulator. Follow this blog for some more techniques to extend the capabilities of the simulator. In the next steps, we’ll discuss how to improve the performance of the current implementation (e.g., by not using a dictionary to store the simulation values), how to turn it into a standalone Q# project, how to provide custom actions for non-intrinsic operations, and how to provide diagnostic operations that help debugging. We are eager to hear your questions or suggestions, and hope to address all of them in upcoming posts in this series!
The post Build your own Q# simulator – Part 1: A simple reversible simulator appeared first on Q# Blog.
]]>