Three reasons results can differ¶
Complete units and sampling first. A measured quantity can change because the physical model is noisy, because only finitely many samples were drawn, or because numerical evolution is approximate. Each cause needs a different check.
| Cause | A useful controlled change | What it does not fix |
|---|---|---|
| Physical noise | Compare ideal and noisy models at the same settings | More shots do not remove physical decoherence |
| Sampling uncertainty | Increase shots or independent repeats | A tighter solver tolerance does not increase the sample size |
| Numerical error | Refine the integration and compare an independent reference | A repeated seed does not establish numerical convergence |
The script below keeps these demonstrations separate. It defines a toy dephasing law ρ01(t) = ρ01(0) exp(−γt) for an initial plus state. Its X expectation is exp(−γt). This explicitly defined γ is not an assertion about the rate normalization of a particular SDK noise channel.
"""Separate a toy dephasing model, sampling uncertainty and integration error."""
from __future__ import annotations
import json
from math import exp, sqrt
import numpy as np
from numpy.typing import NDArray
from scipy.integrate import solve_ivp
from scipy.linalg import expm
def experiment() -> dict[str, object]:
x = np.array([[0, 1], [1, 0]], dtype=complex)
z = np.diag([1, -1])
initial = np.array([1, 0], dtype=complex)
duration = 0.8
def hamiltonian(time: float) -> NDArray[np.complex128]:
return (1.7 * x + 3.0 * time * z) / 2
solution = solve_ivp(
lambda time, state: -1j * hamiltonian(time) @ state,
(0, duration),
initial,
method="DOP853",
rtol=1e-11,
atol=1e-13,
)
if not solution.success:
raise RuntimeError(solution.message)
reference = solution.y[:, -1]
integration = []
for steps in (4, 64):
state = initial.copy()
dt = duration / steps
for step in range(steps):
state = expm(-1j * hamiltonian((step + 0.5) * dt) * dt) @ state
integration.append(
{
"steps": steps,
"state_error_norm": float(np.linalg.norm(state - reference)),
}
)
return {
"toy_dephasing_gamma_per_us": 0.4,
"duration_us": duration,
"x_after_toy_dephasing": exp(-0.4 * duration),
"sampling_se_at_p_half": {str(n): sqrt(0.25 / n) for n in (100, 10000)},
"midpoint_integration": integration,
}
if __name__ == "__main__":
print(json.dumps(experiment(), sort_keys=True))
python examples/learning/foundations/noise_and_precision.py
{
"duration_us": 0.8,
"midpoint_integration": [
{
"state_error_norm": 0.006047460792580759,
"steps": 4
},
{
"state_error_norm": 2.3549057319816534e-05,
"steps": 64
}
],
"sampling_se_at_p_half": {
"100": 0.05,
"10000": 0.005
},
"toy_dephasing_gamma_per_us": 0.4,
"x_after_toy_dephasing": 0.7261490370736908
}
The sampling standard errors are 0.05 and 0.005 for 100 and 10,000 shots at p = 1/2. The integration example uses a time-dependent Hamiltonian with noncommuting terms. It compares a simple midpoint matrix-exponential method with a tightly integrated DOP853 reference. This is a teaching calculation, not the implementation or performance of a CASCAQit simulation method. The reference is numerical too, so convergence against it is a consistency check rather than an exact proof.
Exercise: if increasing shots leaves a discrepancy unchanged, what would you investigate next? If you need a more reliable numerical reference, which setting should you vary?
Reasoning
Check the physical model, units, measurement convention and integration error. Tighten the reference tolerance, refine both methods and check whether the observable has stabilized at the accuracy you need. Do not conclude that the cause is noise merely because sampling error is small.
For SDK settings, read local simulation algorithms and noise models. Next: design a reproducible experiment.
Concept reference: error sources.