terminal
Docs

Get Started with Hardware Noise Model Simulation

Last updated: January 31, 2024
IonQ Staff

Simulating quantum circuits is a critical part of every quantum developer’s toolchain — whether exploring new ideas or making sure that all of the code is ready to run on a remote computer , simulation’s perfect outcomes and fast feedback make developer’s lives easier and more efficient.

But, in our current era of quantum hardware development, real hardware is noisy, and the best algorithm designers know that understanding and tuning their approaches to that noise — whether via error mitigation, noise-adaptive compilation, or noise-resilient circuit design — something that’s impossible to get right without knowing the noise characteristics of the systems they plan to run on.

So, using the IonQ Quantum Cloud, you can now simulate circuits as if they were running on IonQ hardware, using a special noise model simulation. This simulation approach introduces noise into the circuit using a noise model specific to the target hardware — its “noise fingerprint,” if you will — which allows you to have a much more realistic understanding of the performance of your circuit when run on actual hardware.

Hardware noise model simulation is now available for IonQ Aria and Harmony, with a noise model for IonQ Forte coming soon. They are free to use for anyone using the IonQ API directly, including those using it through Google Cloud Marketplace. This guide will provide an example of how to use this capability.

Note: Noise model simulation is a pedagogical tool to help users understand the impact of noise on quantum algorithms. While it can support general understanding, it should not be used to predict if a specific IonQ system will or won't be able to successfully execute a specific circuit.

Please email [email protected] if you need advice on whether your algorithms are likely run successfully on a specific IonQ system.

Running a hardware noise model simulation

The main example in this guide uses curl and our direct API interface to run circuits, so we can show the functionality with as few dependencies as possible. In practice, we recommend using an open-source SDK like Qiskit or Cirq to work with the IonQ API.

To compare the differences between simulation options, we’ll run the same circuit, a simple circuit that builds a Greenberger–Horne–Zeilinger (GHZ) state between nine qubits, on the ideal simulator, the IonQ Harmony simulator, and IonQ Harmony itself. The GHZ state is a maximally-entangled state that’s sometimes called a “Schrödinger’s Cat” state, because the the state is an equal superposition of two opposing outcomes — “alive and dead” in Schrödinger’s famous thought experiment, here simply all zeros or all ones, or |000000000> + |111111111> / sqrt(2) in bra-ket notation.

Here’s the circuit we’ll be using for all three examples:

q_0 ----| H |----@--------------------------------------------| M |	
                 |      									
q_1 -------------X----@---------------------------------------| M |	
                      |      									
q_2-------------------X----@----------------------------------| M |	
                           |      								
q_3 -----------------------X----@-----------------------------| M |	
                                |      							
q_4 ----------------------------X----@------------------------| M |	
                                     |   	   						
q_5 ---------------------------------X----@-------------------| M |	
                                          |      					
q_6 --------------------------------------X----@--------------| M |	
                                               |      				
q_7 -------------------------------------------X----@---------| M |	
                                                    |      				
q_8 ------------------------------------------------X----@----| M |	
                                                         | 				
q_9 -----------------------------------------------------X----| M |

Written in our JSON circuit spec, this would look like:

ghz-nine-qubits.json

{													
  "lang": "json",										
  "target": "simulator",									
  "name": "ghz - nine qubits",								
  "body": {											
    "gateset": "qis",									
    "qubits": 9,										
    "circuit": [										
      {											
        "gate": "h",									
        "target": 0,									
      },											
      {											
        "gate": "cnot",								
        "target": 1,									
        "control": 0									
      },											
      {											
        "gate": "cnot",								
        "target": 2,									
        "control": 1									
      },											
      {											
        "gate": "cnot",								
        "target": 3,									
        "control": 2									
      },											
      {											
        "gate": "cnot",								
        "target": 4,									
        "control": 3									
      },											
      {											
        "gate": "cnot",								
        "target": 5,									
        "control": 4									
      },											
      {											
        "gate": "cnot",								
        "target": 6,									
        "control": 5									
      },											
      {											
        "gate": "cnot",								
        "target": 7,									
        "control": 6									
      },											
      {											
        "gate": "cnot",								
        "target": 8,									
        "control": 7									
      },											
      // note that measurement is implicit in the IonQ syntax		
      // we always measure all qubits at the end of a circuit		
      // if you don’t want all of the qubits’ final states,	you 		
      // can use the meas_map option to generate remapped output	 	
    ]												
  }													
}

Simulating on an ideal simulator

To have something to compare to, let’s first simulate our circuit using the ideal simulator. To do that, we need to add one new item to our above job body, a noise model:

ghz-nine-qubits--ideal.json

{													
  "lang": "json",										
  "target": "simulator",									
  "noise": {"model":"ideal"},									
  "name": "ghz - 9Q - ideal simulation",						
  "body": {											
    "gateset": "qis", 									
    "qubits": 9,										
    "circuit": [										
    // for space, we won’t reproduce the circuit every time — it’s	
    // the same as the first example for all examples in this guide.	
    ]												
  }													
}

Note that for backwards compatibility, submitting to the simulator target with no noise model specified, as in our first example, is the same as submitting with the ideal noise model.

To submit, we’ll need to POST the above job to the IonQ API.

Putting the following two snippets in bash scripts called submit-job.sh and retrieve-job.sh will make your life a little easier when working directly from the command line.

submit-job.sh

curl -X POST "https://api.ionq.co/v0.3/jobs" \						
-H "Authorization: apiKey $1" \								
-H "Content-Type: application/json" \							
-d @$2

retrieve-job.sh

curl "https://api.ionq.co/v0.3/jobs/$2" \							
-H "Authorization: apiKey $1"

Now, we can run our job with one line of code in the command line!

sh submit-job.sh YOUR_API_KEY_HERE ghz-nine-qubits--ideal.json
{													
  "id":"cd097371-05f9-4884-ae00-51b007a93559",					
  "status":"ready",										
  "request":1662602004									
}

And to retrieve the results, we can use our other bash script

sh retrieve-job.sh YOUR_API_KEY_HERE cd097371-05f9-4884-ae00-51b007a93559
{
  "status": "completed",									
  "name": "ghz - 9Q - ideal simulation",						
  "id": "cd097371-05f9-4884-ae00-51b007a93559",					
  "predicted_execution_time": 512,							
  "qubits": 9,											
  "request": 1662602004,									
  "response": 166260205,									
  "start": 166260205,										
  "target": "simulator",									
  "type": "circuit"										
  "data": {											
    "histogram": {										
      "0": 0.5,										
      "511": 0.5										
    },												
    "registers": null									
  },												
  "execution_time": 65,									
  "gate_counts": {										
    "1q": 1,											
    "2q": 8											
  }												
}

Our job is running as-expected so far. We have equal measurements of 0 and 511, or 000000000 and 11111111 in binary. But, because real quantum computers have small amounts of noise, this isn’t what would happen if we ran this circuit on hardware. Let’s see what does happen when we run this on hardware.

Running on hardware

The code for running on hardware is nearly identical; all we have to do is change the target. We’ll also add an explicit shot count, just for clarity (the ideal simulator does not sample shots). This syntactic similarity makes it easy to prototype algorithms in simulation and then run on hardware once all of the kinks have been ironed — just like we’re doing here!

The below code updates the program to run on IonQ Harmony, our widely-available 11-qubit system:

ghz-nine-qubits--qpu-harmony.json

{													
  "lang": "json",										
  "shots": 1000, 										
  "target": "qpu.harmony",									
  "name": "ghz - 9Q - qpu.harmony",							
  "body": {											
    "gateset": "qis", 									
    "qubits": 9,										
    "circuit": [										
    // for space, we won’t reproduce the circuit every time — it’s	
    // the same as the first example for all examples in this guide.	
    ]												
  }													
}

Running it is the same as before if you’ve set up the helpers described above:

submit-job.sh YOUR_API_KEY_HERE ghz-nine-qubits--qpu-harmony.json
{													
  "id":"6da40ecc-8c3e-4f6a-8529-aa513c9e9545",					
  "status":"ready",										
  "request": "1662602669"									
}

For this one, we’ll have to wait a little while because we have to wait in the queue to run on real hardware. Once it’s been run, retrieving it is also the same as before:

retrieve-job.sh YOUR_API_KEY_HERE 6da40ecc-8c3e-4f6a-8529-aa513c9e9545
{														
  "status":"completed",										
  "name":"ghz - 9Q - qpu.harmony",								
  "target":"qpu.harmony",										
  "shots":1000,												
  "predicted_execution_time":11425,								
  "execution_time":7903,										
  "id":"6da40ecc-8c3e-4f6a-8529-aa513c9e9545",						
  "qubits":9,												
  "type":"circuit",											
  "request":1662602669,										
  "start":1662637731,											
  "response":1662637739,										
  "gate_counts":{											
    "1q":1,												
    "2q":8												
  },													
  "data":{												
    "histogram":{											
      "0":0.42,											
      "1":0.004,											
      "2":0.004,											
      "3":0.005,											
      "4":0.003,											
      "7":0.006,											
      "15":0.005,											
      "16":0.002,											
      "31":0.004,											
      "32":0.001,											
      "48":0.001,											
      "63":0.011,											
      "64":0.004,											
      "95":0.001,											
      "120":0.001,										
      "127":0.006,										
      "128":0.001,										
      "192":0.001,										
      "224":0.002,										
      "247":0.001,										
      "251":0.001,										
      "253":0.001,										
      "255":0.023,										
      "256":0.007,										
      "271":0.001,										
      "383":0.001,										
      "384":0.007,										
      "385":0.001,										
      "447":0.006,										
      "448":0.012,										
      "464":0.002,										
      "479":0.007,										
      "480":0.008,										
      "481":0.001,										
      "484":0.001,										
      "495":0.009,										
      "496":0.005,										
      "504":0.004,										
      "506":0.001,										
      "507":0.006,										
      "508":0.002,										
      "509":0.003,										
      "510":0.006,										
      "511":0.402											
    },													
  "registers":null											
  }														
}

Due to hardware noise, these results look a fair bit different than our ideal simulation. For this example, that doesn’t matter too much — we can still clearly see that the 000000000 and 11111111 states dominate the output. But for very deep or complex algorithms, or when running with very few shots, this noise may be the difference between getting the right answer and producing random output.

Hardware noise model simulation lets us approximate the same noise, helping provide insight into what noise will do to our circuit without having to run on a real QPU to find out. Let’s try simulating our circuit with a Harmony noise model.

Simulating using hardware noise model simulation

Finally, we’ll run our circuit as a hardware noise model simulation. The code is very similar to the ones above — we need to change our target back to simulator, and add a "noise" object with the key model and the value harmony. To run as if it were on IonQ Aria, we’d tell the simulator to use model: "aria-1" instead. We’ll leave our shots in, because noise model simulation is shot-aware; that is, it samples “measurements” from the output state based on the number of shots provided.

ghz-nine-qubits--sim-harmony.json

{													
  "lang": "json",										
  "shots": 1000, 										
  "target": "simulator",									
  "noise": { "model": "harmony"}								
  "name": "ghz - 9Q - harmony sim",							
  "body": {											
    "gateset": "qis", 									
    "qubits": 9,										
    "circuit": [										
  // for space, we won’t reproduce the circuit every time — it’s	
  // the same as the first example for all examples in this guide.	
    ]												
  }													
}

Submitting it to the cloud simulation service works the same as before:

submit-job.sh YOUR_API_KEY_HERE ghz-nine-qubits--sim-harmony.json
{													
  "id":"15e8d083-503f-4ebd-9c4d-56d7dfc118cb",					
  "status":"ready",										
  "request": "1662603058"									
}

As does retrieval:

retrieve-job.sh YOUR_API_KEY_HERE 15e8d083-503f-4ebd-9c4d-56d7dfc118cb
{
  "status": "completed",					
  "name": "ghz - 9Q - harmony sim",				
  "target": "simulator",
  "predicted_execution_time": 512,
  "execution_time": 136,
  "id": "2d0cde3c-6811-4d8d-8dcf-0886bf7ece12",
  "qubits": 9,
  "type": "circuit",
  "request": 1665403726,
  "start": 1665403730,
  "response": 1665403730,
  "gate_counts": {
    "1q": 1,
    "2q": 8
  },
  "noise": {
    "model": "harmony-1",
    "seed": 1162484036
  },
  "data": {
    "histogram": {
      "0": 0.47705,
      "1": 0.0009,
      "3": 0.0009,
      "15": 0.0009,
      "31": 0.0009,
      "247": 0.0009,
      "255": 0.0027,
      "383": 0.0018,
      "443": 0.0009,
      "447": 0.0045,
      "479": 0.0036,
      "495": 0.0063,
      "499": 0.0009,
      "503": 0.0063,
      "507": 0.0063,
      "509": 0.0036,
      "510": 0.0018,
      "511": 0.47975
    },			
    "registers": null
  }		
}

Comparing the results

Now, we have three runs of the same circuit on three different backends — an ideal simulator, a hardware noise model simulator, and a real QPU. Let’s graph them together for comparison.

Here's a zoomed-in version so we can see the noise a little better:

As you can see, the IonQ Harmony noise model simulation not only has more noise than the ideal simulation, that noise has a pattern and volume that roughly mimics the noise introduced by the real QPU. That said, please note that it is only broadly similar — the IonQ Harmony simulation and hardware runs are not similar in a statistical sense, only a general one.

While these hardware simulations help users get a better understanding of the system noise characteristics that may impact their algorithms, they are still simplified approximations of what would happen on actual hardware, provided for developer convenience and efficiency, and should not be considered a replacement for running circuits on real hardware. They should not be used to precisely predict what a given IonQ system will or won't be able to successfully execute — if you need help in understanding if your algorithm will successfully run on a specific generation of IonQ hardware, please reach out to [email protected] for guidance.

These are approximate noise models, and their output characteristics will not exactly match the output characteristics of a QPU, especially as you investigate more specific error channels, for example when using our native gate interface to do randomized benchmarking. But, being able to even directionally understand what noise will do to an algorithm can still dramatically improve your understanding and approach to writing quantum programs — imagine a circuit 5x as deep as this one, towards the edge of IonQ Harmony’s #AQ. Understanding the impact of noise would help you select the right number of shots, or otherwise optimize your approach to ensure success.

Gotchas, warnings, and other useful information

Before you go off to simulate your own circuits using hardware noise model simulation, a few other random-access recommendations and notes:

  1. Hardware noise models work with native gates — no special tricks are required here; if you submit a circuit using the native gateset, it will simulate using the native gateset and no compilation, but also with noise.

  2. For backwards compatibility, passing no noise object to the simulator is the same as passing "noise": {"model":"ideal"}. We may deprecate this in future versions.

  3. The "noise" object takes a second, optional parameter called seed. This allows you to specify a specific seed for the pseudo-random noise generation and shot sampling that occurs during noise model simulation, allowing for reproducible “noisy” results. If you don’t provide a seed, one will be chosen at random and then provided as part of the simulation output. The seed can be any integer between 1 and 2 31.

  4. The ideal noise model is NOT shot-aware; that is, it also produces ideal probabilities, without the statistical noise of sampling shots from the ideal state. As such, it ignores any

     “shots” key you pass it. To effectively model the noise, hardware noise model simulations are shot aware and require a shots key. If one is not provided in the request, it will default to our standard 1000 shots.

  5. Because we can’t reasonably characterize and mimic something that doesn’t exist, you can only simulate up to the number of qubits the system has when running a hardware noise model simulations — that is, while our ideal simulator allows you to simulate circuits on up to 29 qubits, the IonQ Harmony simulator only allows circuits up to 11 qubits, and the IonQ Aria simulator only allows circuits up to 25 qubits.

That's all you need to know to begin simulating jobs using hardware noise models on the IonQ Quantum Cloud. Let us know what you use this feature for on our twitter account, @IonQ_Inc, or reach out to reach out to [email protected] to discuss getting access to IonQ Aria or other quantum hardware to use alongside the simulation.

Note: Free usage for all simulators, combined, on the IonQ Quantum Cloud is capped at roughly 30,000 average-sized jobs per month. If you plan to use more simulation time than that, or otherwise want to discuss how IonQ can help you achieve your quantum goals, please reach out to [email protected]