# Tugui Dragos > Self-taught automation builder learning quantum computing in the open. Verified numbers, real IBM hardware, open source tools. Public Ghost content for AI and LLM tooling. This file includes a bounded export of public pages first, then recent public posts. Append `.md` to any post or page URL to get the content in Markdown (for example, `/example-post.md`). ## Pages ### Who I am URL: https://tuguidragos.com/about/ Last updated: 2026-09-13T21:21:31.000Z A page about yourself is a strange thing to write. It is built to be a quiet brag, and I would rather it not be one, so here is the arrangement: I will brag a little, only where it is earned, and keep it in good taste. Everything after that is just true, written plainly. My work is automation. [AI agents that pick up the phone and hold a real conversation](https://github.com/TuguiDragos/ghl-n8n-ai-calling-agent), multi-agent systems that pass a task between them until it is done, publishing pipelines, lead-generation engines wired straight into the tools a business already runs on. I publish workflow templates on [n8n](https://n8n.io/creators/tuguidragos/) as a creator, and I maintain a [verified community node](https://github.com/TuguiDragos/n8n-nodes-ibm-quantum) there. Most of what I build is invisible on purpose, the plumbing under the floor, so the thing you actually see just works, and keeps working while no one is watching it, sometimes for a full working day without a hand on it. That kind of work teaches one habit above all the others. Automation is unforgiving. A wrong assumption does not announce itself. It sits quietly in the pipe and corrupts everything downstream, and you find it three steps too late, after it has already cost you something. So you learn to hold a precise model of how every piece behaves, and you learn to distrust anything you have not watched work with your own eyes. I am entirely self-taught. No computer science degree, no bootcamp, no corporate training program. Just time, a low tolerance for not understanding something, and a refusal to stay where I started. That matters for everything that follows. ## I knew the word long before I understood it Quantum reached me long before I had any claim to it. It came through the television at dinner, while I ate with my parents and the news ran in the background, always in the voice the news keeps for things too large and too far away to touch. Quantum this, quantum that. To me it was not a subject. It was a sound from another world, one I quietly assumed I would never be let into, and I did not lose a moment of sleep over it. I had no reason to think any of it was meant for me. Years went by. The word stayed exactly where it had always been, somewhere above my head, until one ordinary evening it did not. ## The paragraph that would not let go What finally pulled me back was a single paragraph about Grover's algorithm, and I could not put it down. I thought I knew what computation was. I had a clean model of it: a machine that checks one possibility, then the next, then the next, in order, until it lands on the answer. Quantum took that model and broke it in the best possible way. There is a kind of computer that holds many possibilities at once and only commits to one when you force it to look. Not a faster version of the machine I knew. A different idea of what computing even is. What hooked me was not the hype. It was the discomfort. I was holding a model I trusted, and it was wrong, and I could not stand not knowing why. That feeling, the itch of a broken model, is the thing I have chased my whole life. ## The two halves of my head finally met Here is the part I did not expect. The strongest quantum algorithms running on real machines today are not pure quantum. They are hybrid. A quantum processor and a classical computer locked in a loop, passing the problem back and forth, each doing the part it is good at, until the answer settles. I have spent years wiring exactly that shape by hand, systems handing work to each other, none of them useful alone, the whole point living in how they connect. It was instantly familiar and completely new at the same time. That was the moment I knew this was not a passing interest. Quantum is not a different career from the one I have. It is the same instinct, pointed at a stranger machine. ## This is a notebook, not a tutorial This is not a course, and I am not here to teach from a pedestal. It is a notebook, kept in public. A record of one person learning a genuinely hard thing from zero, in the open, getting it wrong first and then getting it right. I write down the parts that bent my brain. The linear algebra I skipped in school and had to crawl back for. The Qiskit code that finally ran after an evening of failing at it. The moment a piece of math stopped being symbols and turned into something I could actually feel. I do not hide the gaps. The gaps are the most useful part of the whole thing, because someone a step behind me is stuck on the exact same one, and the honest version is the one that helps them. Most quantum writing online is one of two kinds. Too shallow to be worth your time, or so deep it assumes a physics PhD you do not have. I am writing the one in between, the version I needed and could not find. Honest, technical, and built from scratch by someone who came in through the side door. ## I run everything before I believe it That same distrust came with me into quantum, and the field gives it plenty to do. A result I have only read about is not one I trust until I have run it myself and watched the numbers land the way the theory promised, or refuse to. That now means real hardware. My circuits have run on ibm\_fez, ibm\_kingston and ibm\_marrakesh, and the machines have disagreed with me often enough to be worth the quota. So the markers here are real and dated. Every IBM Quantum badge I hold is on [Credly](https://www.credly.com/users/tuguidragos/badges), dated and verifiable, and every hardware run has a job ID behind it. The newest of them, from the [Qiskit Global Summer School 2026](https://tuguidragos.com/qiskit-global-summer-school-2026-quantum-excellence-badge/), was earned with a grader and a clock rather than an exam: four labs, the last two on real hardware. It is not a highlight reel. It is a logbook. ## I build the tool I was missing Three times now the same thing has happened. I hit a wall, went looking for the tool that would get me past it, did not find one, and built it. [quantum-exercises](https://github.com/TuguiDragos/quantum-exercises) is where I learn. Hands-on Qiskit exercises and lab notebooks, from an empty laptop to a Bell test that beats the classical bound. When you get one wrong it tells you what your circuit actually did, not what Python thought of your syntax. [qxlint](https://github.com/TuguiDragos/qxlint) is where I check. It reads Qiskit code without running it and catches the failures that do not announce themselves. A circuit with no measurement does not error. It returns zeros, and zeros look like a physics result until you look closer. I ran it over two hundred and forty four public repositories to find out whether it was right. [n8n-nodes-ibm-quantum](https://github.com/TuguiDragos/n8n-nodes-ibm-quantum) is where it leaves my laptop. An action node and polling triggers, one credential, no runtime dependencies, verified by n8n so it installs on Cloud. Real circuits on real hardware, called from a workflow like anything else I wire. Learn it, check it, wire it. Each one started as something that cost me an evening. All three are MIT and public. ## Where this is going I came from automation, and the bet I am making is a specific one. Right now you coax results out of a quantum processor mostly by hand. I think that changes. It becomes one more piece you wire into a system, a service you call, monitor, retry, and orchestrate, exactly the way I already wire everything else. The engineering around the machine, not only the physics inside it, is the part my background points straight at. It is the same job I have always done. This time the thing out front is a quantum processor. I cannot see the whole road. I can see the next step, so I take it, and the notebook gets one entry longer. ## Where to find me ### Privacy URL: https://tuguidragos.com/privacy/ Last updated: 2026-09-13T20:00:37.000Z Short version: I collect nothing about you. **No analytics.** No Google Analytics, no Plausible, no Cloudflare Web Analytics. I do not know how many people read this, which posts land, or where you came from. I write it anyway. **No cookies.** Not one. Open DevTools and look: the site sets nothing, stores nothing in localStorage, registers no service worker. There is no consent banner because there is nothing to consent to. **No third-party scripts.** Every file this page loads comes from this domain. No fonts from Google, no embedded widgets, no social pixels. If a post ever carries a YouTube embed, it stays blocked until you click it, and then loads from youtube-nocookie.com. **No accounts, no newsletter, no comments.** There is nothing to sign up for, so there is no list your address could sit on. ## What does happen The site runs on a server I rent, behind Cloudflare. Both keep ordinary request logs with IP addresses, the way every site on the internet does. I do not read them and I build nothing from them. That is plumbing, not a product. If you email me, I have your email. It stays in my mailbox and nowhere else. ## Search engines and language models Both are welcome, deliberately. This is a notebook of what I am learning, and it is worth more if it can be found and quoted. robots.txt allows the usual crawlers, and the site publishes an llms.txt so language models can read it cleanly. Two exceptions: Bytespider and Amazonbot are blocked. Not on principle, they are just aggressive and give nothing back. ## That is the whole policy No data, no cookies, no tracking. A notebook where I post my work, left open to be read. Nothing more, nothing less. Written 16 August 2026\. If this ever stops being true, this page changes first. ## Posts ### Phase kickback: the trick behind quantum algorithms URL: https://tuguidragos.com/phase-kickback-the-trick-behind-quantum-algorithms/ Last updated: 2026-10-03T13:35:15.000Z I bounced off quantum algorithms for a while. Every explanation reached a point where you "query the oracle" and somehow the answer materialised, and that step always felt like a magic trick I was supposed to nod along with. The function gets evaluated, and then, hand wave, the qubits know something. I could follow the code without understanding the move. The thing that finally unlocked it was a single mechanism with an odd name: phase kickback. It is the move underneath almost every quantum algorithm, the actual mechanics of how an oracle does its work, and once I saw it clearly, half the algorithms stopped being black boxes. This post is just that one move, slowly, with the code to watch it happen. ## The setup you already have Two ideas from earlier posts come together here. From [the phase post](https://tuguidragos.com/relative-phase-the-invisible-engine-of-quantum/), relative phase is real information that a measurement cannot see directly but the next gate can. From [the gates post](https://tuguidragos.com/quantum-gates-are-just-matrices-you-can-run-by-hand/), every gate is a matrix that acts on a state. Phase kickback lives exactly at the intersection: it is a way to make a gate write a phase onto a qubit you did not act on directly. To see it, you need one new idea, and it is simpler than its name. ## Eigenstates, in plain words An eigenstate of a gate is a state the gate does not really change. Apply the gate, and the state comes out pointing the same way it went in, only possibly scaled by a number. That number is called the eigenvalue. Most states get genuinely transformed by a gate, rotated to somewhere new. An eigenstate is special: the gate just multiplies it by a constant and leaves its direction alone. The X gate has a beautiful one. The state |−⟩, which is (|0⟩ minus |1⟩) over √2, is an eigenstate of X, and its eigenvalue is minus one. ```python import numpy as np minus = np.array([1, -1]) / np.sqrt(2) X = np.array([[0, 1], [1, 0]]) print(np.allclose(X @ minus, -minus)) # True ``` Run it. Applying X to |−⟩ gives back exactly |−⟩, multiplied by minus one. X did not turn |−⟩ into some other state. It just stamped a minus sign on it and handed it back unchanged in direction. That minus sign, that eigenvalue, is the phase we are about to put to work. The whole trick hinges on this one fact: X leaves |−⟩ alone except for a sign. For completeness, X has a second eigenstate, and it is the partner you would guess. The state |+⟩, which is (|0⟩ plus |1⟩) over √2, is also an eigenstate of X, but its eigenvalue is plus one: X leaves |+⟩ entirely alone, sign and all. ```python plus = np.array([1, 1]) / np.sqrt(2) print(np.allclose(X @ plus, plus)) # True, eigenvalue +1 ``` Run it. So X has two eigenstates, |+⟩ with eigenvalue plus one and |−⟩ with eigenvalue minus one, and the interesting one for us is |−⟩, because its minus one is a phase worth kicking around. |+⟩ would kick back a phase of plus one, which is no change at all, so it does nothing visible. The minus sign on |−⟩ is where the action is. ## The kickback itself Now the move. Take a controlled X gate, a CNOT, with the control qubit in |+⟩ and the target qubit in |−⟩. Your instinct, trained on classical logic, says the control acts on the target: the control decides whether the target flips. Watch what actually happens. ```python import numpy as np from qiskit import QuantumCircuit from qiskit.quantum_info import Statevector qc = QuantumCircuit(2) qc.h(0) # control, qubit 0, becomes |+> qc.x(1); qc.h(1) # target, qubit 1, becomes |-> qc.cx(0, 1) # CNOT: control q0, target q1 print(np.round(Statevector(qc).data.real, 3)) # [0.5 -0.5 -0.5 0.5] ``` Run it. The resulting state is \[0.5, minus 0.5, minus 0.5, 0.5\], and the important thing is what that factors into. It is |−⟩ on the control [tensored](https://tuguidragos.com/the-tensor-product-why-10-qubits-means-1024-numbers/) with |−⟩ on the target. The target is still |−⟩, exactly as it started, completely unchanged. But the control is no longer |+⟩. It has become |−⟩. The control qubit, the one that was supposed to be doing the acting, is the one that changed. That is phase kickback. Here is why it happened. The CNOT only does something on the branch where the control is |1⟩, and on that branch it applies X to the target. But the target is |−⟩, and X just multiplies |−⟩ by minus one. So the |1⟩ branch of the control picks up a minus sign, while the |0⟩ branch does not. A minus sign on the |1⟩ part and not the |0⟩ part is precisely the difference between |+⟩ and |−⟩. The phase that the operation produced on the target got kicked back up onto the control, flipping it from |+⟩ to |−⟩, while the target sailed through untouched. ## What happens without the eigenstate To see that the eigenstate is the essential ingredient, change one thing and watch the trick fail. Keep the control in |+⟩, but put the target in plain |0⟩ instead of |−⟩, and apply the same CNOT. ```python qc = QuantumCircuit(2) qc.h(0) # control still |+> qc.cx(0, 1) # target is plain |0>, not an eigenstate print(np.round(Statevector(qc).data.real, 3)) # [0.707 0 0 0.707] ``` Run it. Now you get \[0.707, 0, 0, 0.707\], which is the Bell state, the entangled pair from [the entanglement post](https://tuguidragos.com/entanglement-explained-without-the-spooky/). The control and target are now tangled together, and neither has a clean separate state at all. No phase got kicked cleanly onto the control, because |0⟩ is not an eigenstate of X, so X genuinely transforms it rather than just stamping a phase. The whole magic of kickback depended on the target being an eigenstate, a state the gate leaves alone except for a phase. Feed the gate a non eigenstate and instead of a clean phase kick you get entanglement, a messier outcome where the action does not move cleanly upstream. The eigenstate is not a detail. It is the entire reason the phase comes back clean. ## Control and target are just labels One more thing that deepened my understanding, and it is a little unsettling at first. We keep saying control and target as though the control acts on the target. Kickback already strained that, since the control changed and the target did not. The controlled Z gate, which flips the sign only when both qubits are 1, strains it further: it is completely symmetric between the two qubits. ```python import numpy as np CZ = np.diag([1, 1, 1, -1]) print(np.allclose(CZ, CZ.T)) # True: the matrix is its own transpose ``` Run it. The controlled Z matrix is its own transpose, and more to the point it is diagonal, with the minus sign sitting only on |11⟩, so swapping which qubit you call the control and which the target changes nothing whatsoever. There is no fact of the matter about which one is in charge. The labels control and target are a convenience we impose, a story we tell about a two qubit gate, not something the physics distinguishes. Once you accept that, kickback stops being paradoxical. Of course the phase can land on the control, because control was never a privileged role to begin with. A two qubit gate acts on both qubits together, and which one ends up carrying a phase depends on their states, not on our names for them. ## Why this is the whole trick Sit with the strangeness for a second, because it is the good kind. We applied a gate whose entire job is to flip the target, and the target did not flip. Instead the control changed. The target acted like a catalyst: it made a phase happen, that phase landed on the control, and the target came out exactly as it went in. The action moved upstream, from target to control, carried by a phase. And it generalises. This was X and |−⟩, but the same thing works for any controlled gate whose target sits in an eigenstate of that gate. The eigenvalue, the phase the gate would stamp on its eigenstate, gets kicked back onto the control instead. Put the target in the right eigenstate and you have turned a controlled operation into a phase writer for the control qubits. That is a tool, and it is the tool the algorithms are built from. ## Any phase, not just a sign The minus one from X is the simplest case, but the eigenvalue that gets kicked back can be any phase at all, and that generality is where some of the deepest algorithms live. A phase gate like S or T has |1⟩ as an eigenstate, with an eigenvalue that is not minus one but a partial turn, i for S, a forty five degree turn for T, exactly the rotations from [the complex numbers post](https://tuguidragos.com/complex-numbers-are-the-gateway-to-quantum/). A controlled version of such a gate, with its target in |1⟩, kicks that partial phase back onto the control rather than a mere sign. So kickback is not limited to flipping signs. It can write any rotation onto the control, as finely as you like. This is the seed of one of the most important quantum algorithms, phase estimation, which I will not unpack here but which is worth naming. The whole idea of phase estimation is to take a gate whose eigenvalue encodes some quantity you want, kick that eigenvalue back onto a register of control qubits as a precise phase, and then use interference to read the phase out as a number. It is kickback turned into a measuring instrument: a way to extract the hidden phase of a gate, digit by digit. The famous applications, including the quantum part of Shor's factoring algorithm, are built on exactly this. You do not need the details yet. You just need to see that the simple sign flip you watched above is the small end of a tool that scales all the way up to the algorithms that make quantum computing genuinely powerful. ## How oracles actually use it Here is where it connects to the magic I could not follow. A quantum oracle is a gate that encodes some function f. Run it naively and it just computes f into an output qubit, which is not obviously useful. But arrange things with phase kickback, put the output qubit (the ancilla) into the |−⟩ eigenstate, and something much better happens. The oracle's answer, the value of f, becomes a phase stamped onto the input qubits, the controls, instead of a value sitting in the output. The function's output has been kicked back into the phases of the inputs. Now recall the engine from the phase post. A relative phase is invisible to a direct measurement, but a layer of Hadamards turns relative phases into measurable outcomes through interference. So the full pattern is two beats. Phase kickback is the write: the oracle stamps f's answer into the phases of the input register, where no measurement can yet see it. Interference is the read: a Hadamard layer converts those phases into a definite outcome you can measure. Kickback writes the answer into phase, interference reads it back out. That two beat rhythm, write into phase then interfere to read, is the skeleton of nearly every quantum algorithm there is. ## What this unlocks I am not going to run a full algorithm here, because each one deserves its own post and the next several are exactly that. But every one of them is this move, dressed differently. Deutsch and Jozsa use kickback to detect a global property of a function in one query. Bernstein and Vazirani use it to read an entire hidden string in one shot. Grover uses it to mark a search target with a phase so interference can amplify it. Different problems, different oracles, same underlying trick: get the answer into a phase with kickback, then interfere to read it. Once you see that the oracle is not magic but a phase writer, the algorithms stop being black boxes and start being variations on a theme you understand. ## A quick test before you move on Close this and answer in your own words. What is an eigenstate of a gate, and what is the eigenvalue? If you cannot say a state the gate leaves pointing the same way, scaled by a number, reread the eigenstate section, because everything rests on it. In the kickback circuit, the CNOT was supposed to flip the target. What actually changed, and why? If your answer is not the control changed, because the minus sign from X on the |−⟩ target landed on the control's |1⟩ branch, reread the kickback section. And what are the two beats of nearly every quantum algorithm? If write the answer into phase, then interfere to read it is not roughly your answer, revisit the oracle section, because that rhythm is the thing the next posts all run on. ## Where I am learning it Free. Phase kickback is covered in the IBM Quantum learning materials and in most serious algorithm introductions, though I found that no description landed until I built the two qubit circuit above and watched the control flip when it had no business flipping. The eigenstate idea comes straight from linear algebra, so the 3Blue1Brown series helps if eigenvectors are hazy. As always, I keep this as a tiny runnable circuit, because a phase that I have only read about getting kicked back does not convince me until I have watched the control qubit change on my own machine. ## The magic was one move For a while, oracles were the wall I could not get past, the place every algorithm explanation turned into hand waving. The wall turned out to be one mechanism. A gate acts on a target that happens to be an eigenstate, the eigenvalue's phase kicks back onto the control, and suddenly a function's answer is sitting in the phases of qubits the gate never touched, ready for interference to read out. That is phase kickback, and it is the engine room of quantum algorithms. Not a different trick for each one, but the same move, reused. The next few posts are all this, applied to actual algorithms, and they will feel less like magic and more like recognition. You have already seen the move. The rest is watching it work. ### From counts to histogram: reading quantum results URL: https://tuguidragos.com/from-counts-to-histogram-reading-quantum-results/ Last updated: 2026-09-29T20:33:55.000Z Almost every quantum circuit I have run in this series ends the same way: a dictionary of counts. Something like {'00': 528, '11': 496}. It is the output of the whole machine, the thing you actually read to find your answer, and for a while I treated it as a slightly mysterious object that quantum hands you at the end. It is not mysterious at all. It is a plain Python dictionary, and reading it is ordinary Python you already know. This post takes that output apart completely, in plain code, so the last bit of mystery drains out of the result. ## What the counts dictionary actually is When you run a circuit for some number of shots, the machine runs it that many times and records what it measured each time. The counts dictionary is just the tally. The keys are the outcomes, as bit strings, and the values are how many times each outcome came up. ```python counts = {'00': 528, '11': 496} ``` That is the entire object. It says: out of the shots I ran, 528 came back as 00 and 496 came back as 11\. Nothing else happened, those were the only two outcomes seen. If you have ever counted occurrences of anything in Python, with a dictionary or Python's collections.Counter, this is exactly that, and the quantum machine is just the thing that generated the tally. The mystery, if there was one, is gone already. It is a histogram in dictionary form. ## From counts to probabilities The raw counts depend on how many shots you ran. To get something comparable and meaningful, you convert to probabilities, which is just each count divided by the total. This is the Born rule from [the measurement post](https://tuguidragos.com/measurement-is-not-magic-the-born-rule-by-hand/), arriving as plain arithmetic: the fraction of shots that gave an outcome is your estimate of that outcome's probability. ```python total = sum(counts.values()) probabilities = {outcome: round(n / total, 3) for outcome, n in counts.items()} print(probabilities) # {'00': 0.516, '11': 0.484} ``` Run it. You divide each count by the total number of shots and get the probability of each outcome, roughly fifty fifty here, exactly what a Bell state should give. This is the single most useful transformation you will do on quantum output, because probabilities are what the theory predicts and what you compare against. The counts are the raw data. The probabilities are the answer, and turning one into the other is a one line dictionary comprehension. No quantum knowledge required for this step, just division. ## Reading it like a result, not a blob A dictionary in arbitrary order is hard to read at a glance, so the next thing I do is sort it, usually by outcome so the bit strings line up in order, sometimes by count to see the winners first. Sorting quantum output is the same as sorting any dictionary. ```python total = sum(counts.values()) for outcome in sorted(counts): p = 100 * counts[outcome] / total print(f"|{outcome}> {p:5.1f}% ({counts[outcome]} shots)") ``` Run it. Now the outcomes come out in a tidy, ordered list with their percentages and raw counts side by side, which is far easier to scan than a jumbled dictionary. This tiny bit of formatting is the difference between staring at a blob and reading a result. And it generalises straight to bigger circuits: a three qubit result has up to eight keys, 000 through 111, and the same sorted loop lays them out in clean binary order, exactly the slot ordering from [the tensor product post](https://tuguidragos.com/the-tensor-product-why-10-qubits-means-1024-numbers/). ## A histogram you can read in the terminal You do not always need a fancy plot. A surprising amount of the time, a text histogram printed right in your terminal tells you everything you need, instantly, with no plotting library at all. The trick is to turn each probability into a bar of characters. ```python counts = {'00': 496, '01': 35, '10': 31, '11': 530} total = sum(counts.values()) for outcome in sorted(counts): p = counts[outcome] / total bar = '#' * round(p * 40) # 40 characters = 100% print(f"|{outcome}> {p*100:5.1f}% {bar}") ``` Run it. You get a little bar chart in plain text, each outcome's bar as long as its probability, and the shape of the result jumps out immediately. In this example, 00 and 11 have long bars and 01 and 10 have stubs, which is exactly the picture from [the real hardware post](https://tuguidragos.com/i-ran-a-bell-state-on-real-ibm-quantum-hardware/): the two correct outcomes dominate, with a little noise leaking into the impossible ones. You can read the health of a run at a glance from the bar lengths, and you wrote the whole chart in four lines of standard Python. I reach for this constantly, because it is instant and it travels anywhere a terminal does. ## When you do want the real plot Qiskit ships a proper plotting helper for when you want a clean image, for a notebook or to share. It is called plot\_histogram, it lives in the visualization module, and you hand it the counts dictionary directly. ```python from qiskit.visualization import plot_histogram import matplotlib.pyplot as plt counts = {'00': 496, '01': 35, '10': 31, '11': 530} plot_histogram(counts) plt.show() ``` This needs matplotlib installed, and it pops up the familiar bar chart with the outcomes on one axis and counts on the other. It is the right tool when you want something polished. But notice what it is doing under the hood: exactly what the four line text version did, counts to bars. The library version is prettier and saves you the formatting, and it is genuinely handy, but it is not doing anything you could not do yourself, and knowing that is the point. The plot is convenience, not magic. ## Reading one qubit out of many: the marginal On a bigger circuit you often care about just one qubit, not the full joint outcome. The counts dictionary has the full bit strings, so to read a single qubit you sum the counts over all the others, which is called taking a marginal. One subtlety first: Qiskit writes its bit strings little endian, qubit 0 on the right, the same convention that has come up in the [Python](https://tuguidragos.com/the-python-you-actually-need-for-quantum-computing/) and [gates](https://tuguidragos.com/quantum-gates-are-just-matrices-you-can-run-by-hand/) posts. So the rightmost character of each key is qubit 0. ```python from collections import defaultdict counts = {'00': 496, '01': 35, '10': 31, '11': 530} marginal_q0 = defaultdict(int) for bits, n in counts.items(): marginal_q0[bits[-1]] += n # rightmost char = qubit 0 print(dict(marginal_q0)) # {'0': 527, '1': 565} ``` Run it. By summing over everything except the last character, you collapse the four outcomes down to just what qubit 0 did, ignoring qubit 1 entirely. Here qubit 0 came out roughly balanced, which for a Bell state is exactly right, since each qubit alone looks like a coin, the marginal point from [the entanglement post](https://tuguidragos.com/entanglement-explained-without-the-spooky/). This is the same operation as a pivot or a group by in any data work: you have detailed records and you aggregate away the columns you do not care about. Pick the character position for the qubit you want, sum, done. No quantum reasoning in the code, just grouping a dictionary by part of its key. ## Comparing two results: how far from ideal Often you want to know how close a real, noisy result is to the ideal one, a single number for the gap. A simple, honest one is the total variation distance: turn both into probabilities, take the absolute difference outcome by outcome, add them up, and halve. Zero means identical, one means completely different. ```python counts = {'00': 496, '01': 35, '10': 31, '11': 530} total = sum(counts.values()) measured = {k: v / total for k, v in counts.items()} ideal = {'00': 0.5, '11': 0.5, '01': 0.0, '10': 0.0} outcomes = set(measured) | set(ideal) tvd = 0.5 * sum(abs(measured.get(o, 0) - ideal.get(o, 0)) for o in outcomes) print(round(tvd, 4)) # 0.0604 ``` Run it. You get about 0.06, which says this example run is close to ideal but not perfect, and conveniently it lines up with the roughly six percent of shots that landed in the wrong bins in this example. One number, easy to compute, that summarises how far a result drifted from what it should have been. It is the kind of metric you compute to track whether a device or a circuit is behaving, and it is nothing but dictionary lookups, subtraction, and a sum. The statistics are standard. The quantum part already happened upstream. ## The top outcomes, when there are many For a circuit with many qubits you can get dozens of outcomes, most of them tiny, and you usually only care about the few biggest. Sorting by count and taking the top handful is the move, the same top-k you would do with any tally. ```python counts = {'00': 496, '01': 35, '10': 31, '11': 530} top = sorted(counts.items(), key=lambda kv: kv[1], reverse=True)[:2] print(top) # [('11', 530), ('00', 496)] ``` Run it. You sort the items by their count, highest first, and slice off the top two, leaving the noise behind. On a large result this is how you cut through a cluttered histogram to the outcomes that actually carry the signal, which are usually the answer you ran the circuit to find. Sort, reverse, slice. The exact pattern you use to find the most common anything in Python, pointed at quantum output. ## Saving results for later One more practical habit, because real work means coming back to results, not just glancing at them once. A counts dictionary is plain data, so you can save it the way you save any data, and the obvious choice is JSON, since the dictionary maps onto it directly. ```python import json counts = {'00': 496, '01': 35, '10': 31, '11': 530} with open("run_result.json", "w") as f: json.dump(counts, f) # later, or in another script with open("run_result.json") as f: loaded = json.load(f) print(loaded) # {'00': 496, '01': 35, '10': 31, '11': 530} ``` Run it. You write the dictionary to a file and read it back identically, and now a run from today is something you can compare against a run from next week, or feed into a notebook, or hand to another tool. This matters more than it sounds, because quantum results are precious. A run on real hardware cost you a slot in a queue and a few seconds of an expensive machine, so throwing the numbers away after one look is wasteful. Save them. If you build automation pipelines, this is reflex: you persist anything that was costly to produce, and quantum output is exactly that. JSON for the structured dictionary, or a CSV row if you are logging many runs over time, and suddenly your experiments have a history you can analyse instead of a series of glances you half remember. ## Putting it together: parse, summarise, decide In practice these steps chain into a tiny analysis function, the kind of thing you write once and reuse on every result. Take counts, compute probabilities, find the most likely outcome, report it. That last step, picking the winner, is often the actual answer you ran the circuit for. ```python def summarise(counts): total = sum(counts.values()) probs = {k: v / total for k, v in counts.items()} best = max(probs, key=probs.get) print(f"most likely outcome: |{best}> at {probs[best]*100:.1f}%") return probs summarise({'00': 496, '01': 35, '10': 31, '11': 530}) # most likely outcome: |11> at 48.5% ``` Run it. It tells you the most probable outcome and its probability, which for many circuits is precisely the thing you wanted to know. This is the bridge from [the pipeline post](https://tuguidragos.com/running-a-quantum-job-like-an-automation-pipeline/), where the result came back as structured data, to an actual decision: you parse the counts, turn them into probabilities, and read off the answer. It is all standard Python, dictionaries and division and max, with no quantum specific machinery anywhere in the analysis. The quantum was in generating the counts. Everything after is just reading a tally. ## A quick test before you move on Close this and answer in your own words. What are the keys and the values in a counts dictionary? If you cannot say the keys are outcome bit strings and the values are how many times each came up, reread the first section. How do you turn counts into probabilities, and why is that the step that matters? If divide each count by the total is not your answer, and you cannot connect it to the Born rule, revisit the probabilities section. And what is plot\_histogram actually doing that you could not do in a few lines yourself? If you cannot describe counts to probabilities to bars, reread the section on the text histogram, because the whole point was that none of this is mysterious. ## Where I am learning it Free, and most of it is just Python you may already have. The Qiskit documentation covers plot\_histogram and the visualization tools, and the broader Python skills, dictionaries, comprehensions, sorting, formatting, are the same ones from any solid Python introduction. The text histogram trick is not from any course, it is just a habit worth stealing, because a chart you can print anywhere beats one that needs a plotting stack. As always, I learned this by running real circuits and parsing their output by hand, because a result I have only had a library hand me prettily is not one I fully understand until I have torn the dictionary apart myself. ## The output was never the mystery For a while, the counts dictionary felt like the one genuinely quantum thing at the end of a circuit, the mysterious payload the machine hands back. It is the least mysterious part of the whole process. It is a tally, a plain Python dictionary of outcomes and how often they happened, and reading it is division, sorting, and a little formatting, the most ordinary code there is. That was a relief to internalise, and it is a pattern worth carrying. The quantum strangeness, if it is anywhere, is in the superposition and the interference and the entanglement, in how the counts came to be. The counts themselves are just data, and you already know how to read data. The exotic part ends at the measurement. Everything after is Python you have written a hundred times. ### The honest state of quantum in 2026 (NISQ, no hype) URL: https://tuguidragos.com/the-honest-state-of-quantum-in-2026-nisq-no-hype/ Last updated: 2026-10-03T13:35:14.000Z I came into quantum computing through a wall of hype. Quantum will break all encryption. Quantum will cure disease, crack fusion, redesign every material, and change the world by next Tuesday. It is the future, get in now, or be left behind. That noise is what I had to unlearn first, and I am a little embarrassed by how much of it I had absorbed, because almost none of it survives contact with the actual field. Then I started learning for real. I did the math, I built circuits, I [ran a Bell state on an actual quantum computer](https://tuguidragos.com/i-ran-a-bell-state-on-real-ibm-quantum-hardware/) and watched just over one percent of the answers come back impossible. And slowly I built a picture that is neither the hype nor its mirror image, the cynical "it is all a scam" take that is just as wrong. Here is that picture, honestly, with no marketing and no doom. This is where quantum computing actually is in 2026, what it genuinely cannot do, and why I still think it is worth starting now. ## What NISQ actually means If you learn one piece of vocabulary, learn this one, because it is the most honest three word summary of the entire present moment. We are in the NISQ era. Noisy Intermediate-Scale Quantum. Each word is doing real work. Noisy: the hardware makes errors, constantly, the way the hardware post and the decoherence post showed. Gates are imperfect, measurements misread, and [the qubits forget their state](https://tuguidragos.com/why-your-qubits-forget-decoherence-in-plain-words/) on a timescale of microseconds. Every computation is a race against accumulating noise. Intermediate-Scale: today's machines have on the order of hundreds of physical qubits. That sounds like a lot until you learn that the genuinely world changing applications need many thousands or millions of high quality ones. We have intermediate scale, not the scale that matters. Quantum: it is real. These are genuine quantum machines doing genuine quantum things, entanglement and interference and all of it. Just constrained, hard to use, and noisy. Put together, NISQ describes a technology that is real but not yet useful at scale, powerful in principle but hemmed in by noise and size in practice. In 2026 we are squarely inside this era. Not at the start of it, the hardware has improved a lot, but not out of it either. That is the ground truth everything else sits on. ## What quantum genuinely cannot do today Here is the part the hype skips, stated plainly, because being clear about the limits is where the honesty lives. It will not break your encryption. This is the big one, the headline that scared everyone, and it is not happening soon. The algorithm that threatens current encryption needs to run deep, clean circuits on a vast number of high quality qubits. Recent estimates have actually brought the required number down, with one result suggesting it might take on the order of a million qubits rather than the tens of millions once assumed, but a million physical qubits running under error correction is still wildly beyond today's hundreds of noisy ones. The gap is not a quarter or two. It is years to decades, and anyone telling you your data is at risk this year is selling something. Put a number on the gap. ```python # the encryption-breaking gap, in round numbers qubits_today = 1_000 # noisy physical qubits, a strong machine in 2026 qubits_needed = 1_000_000 # physical qubits under error correction, RSA-2048 (2025 estimate) print(f"{qubits_needed // qubits_today}x more qubits, and the error corrected kind") # 1000x ``` Run it. A thousandfold, and the real gap is worse than the number, because today's qubits are noisy while the million would have to run under error correction, which does not exist at that scale yet. That is why the honest estimate is years to decades, not quarters. It is not a faster classical computer. Quantum will not speed up your website, your spreadsheet, your database, or almost any ordinary code you write. It is not a drop in upgrade that does everything faster. It is good at a narrow class of problems with a particular structure, and useless or worse than classical for the overwhelming majority of computing tasks. The mental image of a quantum chip that just makes all software faster is simply wrong. It cannot run the famous algorithms at useful sizes. You can run Shor's algorithm to factor the number fifteen as a demo. You cannot run it on a number big enough to matter, because that needs depth and qubit counts and error rates that do not exist yet. Same for Grover and the rest. The algorithms are real and the small demonstrations work, but useful scale is not here. There is no large scale error correction yet. The noise from the hardware posts is not currently being corrected away by error correction proper. The field knows how error correction should work in theory, and there are real laboratory demonstrations of the pieces, but a machine with enough stable, error corrected logical qubits to run a serious computation does not yet exist. This, more than raw qubit count, is the real bottleneck. ## What is actually true and genuinely promising Now the other side, because cynicism is as lazy as hype. Several things are real, improving, and worth taking seriously. The field has pivoted from more qubits to better qubits. For years the headlines were a qubit count arms race, which was always a bit of a vanity metric. That has shifted. The number people now watch is the stability and fidelity of the qubits, and increasingly the performance of logical qubits, the error corrected kind. Gate fidelities have climbed to impressive figures on the best hardware, and that quality, not raw count, is the metric that actually moves the field toward usefulness. That shift in emphasis is healthy and real. Hybrid quantum-classical is the realistic near-term path. Nobody serious expects a pure quantum computer to take over a whole problem soon. The pragmatic approach, and the one behind [the variational algorithms](https://tuguidragos.com/three-quantum-ways-to-diagonalize-a-giant-matrix/) that use the parameterised circuits from [the pipeline post](https://tuguidragos.com/running-a-quantum-job-like-an-automation-pipeline/), is to do a small, hard, quantum specific core on the quantum machine and let classical computers handle everything around it. That hybrid shape is where near-term value, if it comes, will come from. There are domains with the right shape. Quantum chemistry and materials science, simulating quantum systems with a quantum machine, certain optimisation problems, some areas of finance. These share a structure that fits what quantum is good at, they tolerate approximate answers, and they already fund real research programs. The promise here is narrow and early, but it is genuine, not vapour. The roadmaps point somewhere real, if you read them right. The major players publish roadmaps targeting fault tolerant modules, machines that correct their own errors as they run, and logical qubits across the late 2020s and into the 2030s. I take these as aspirational direction, not promises, because timelines in this field have a long history of slipping. But the direction is real and the investment behind it is enormous. ## A word on quantum advantage You will hear the phrases quantum advantage and quantum supremacy, and they are worth pinning down because they get badly misused. Quantum supremacy means a quantum computer doing some task, any task, faster than the best classical computer can. It has been claimed and demonstrated, but the catch is enormous: the tasks chosen were contrived, designed specifically to be hard for classical machines and easy for quantum ones, with no practical use whatsoever. It was a milestone, a proof that the hardware can in principle outrun classical computing on something, and it was genuinely significant as a scientific marker. It was not a useful computation, and the goalposts have moved more than once as classical algorithms improved to claw back some of those claims. What actually matters, and what the field is chasing now, is quantum advantage on a useful problem: a quantum computer solving something people genuinely care about, faster or better or cheaper than any classical approach. As of 2026 that is contested ground rather than a line cleanly crossed. The supremacy demonstrations proved the hardware can win a rigged race. Winning a race that matters, on a problem with real value, is the threshold the whole field is still working toward, and it is the one to watch. When someone claims quantum advantage, the only question worth asking is whether the problem was useful or contrived. Until recently the honest answer for every clean demonstration was contrived. Now a few practical claims have appeared, a modest edge on a niche simulation, a chemistry result, and the argument has shifted from whether there is any advantage to whether a given one truly counts, with none yet uncontested or broadly useful. The useful version is still ahead of us. ## The realistic timeline, without pretending I know So when does it get genuinely useful? Honestly, nobody knows, and anyone who gives you a confident year is guessing. The defensible shape is this. We are in NISQ now. Lab scale logical qubits are emerging and will mature over the next few years. Multi logical qubit, error corrected systems scale through the latter half of this decade if things go well. Production, broadly useful, fault tolerant machines are most plausibly a 2030s story, contingent on hard breakthroughs in materials, control, and software that may or may not arrive on schedule. And NISQ machines will not vanish when fault tolerant ones arrive, the two will coexist for years, the way older and newer computing paradigms always overlap. Hold that loosely. It is a reasonable central estimate, not a prophecy, and the error bars are wide in both directions. The honest answer to "when" is "later than the hype says, and nobody can tell you exactly." ## So why enter early Given all that, why am I spending my evenings on this instead of waiting for the dust to settle? A few reasons, and they are the whole thesis of why I am doing this in public. The learning curve is long, and you cannot cram it. The math, the linear algebra, the complex numbers, the intuition for phase and interference and entanglement, takes a year or more to build properly. It is not the kind of thing you absorb in a weekend once the useful hardware lands. If you want to be ready to do real work when the machines mature, the time to be climbing the curve is now, slowly, while there is no pressure. Being early here is not about the hardware. It is about the understanding. Depth is the differentiator, and it is getting rarer. As AI makes shallow content cheap, and it absolutely does, the durable edge shifts to genuine understanding, the kind you cannot fake or generate. Quantum is a domain where that depth is scarce and valuable, where the gap between someone who has truly internalised the concepts and someone who has skimmed a few articles is enormous and obvious. Building that depth now, while it is still rare, compounds. Being early on the right side. I am not betting on a quick payday. There may not be one for years, and I have made my peace with that. I am building a foundation in a hard, real field while the foundation is still uncommon, which is a very different bet from chasing the hype for a fast return. One is fragile. The other compounds quietly regardless of when the hardware catches up. And the honest framing is itself the value. There is an ocean of breathless quantum content out there, and a smaller pool of pure cynicism, and very little that is calibrated, no hype, and clearly written by someone who actually did the math. That calibrated honesty is rare, and rare is valuable. Being the person who can tell you exactly what quantum can and cannot do, without the marketing, is worth something precisely because so few people bother. There is also a quieter reason that is specific to coming from where I do. A lot of what surrounds quantum computing is not exotic physics at all, it is ordinary engineering wearing unfamiliar names, as the pipeline post and [the version trap post](https://tuguidragos.com/the-version-trap-that-cost-me-an-evening-qiskit-2-x/) argued: jobs, queues, compilation, parsing results, version management, working around an unreliable backend. If you already build software, a real fraction of the quantum stack is stuff you know, and that means your runway into the field is shorter than the hype, in either direction, lets on. You are not starting from zero. You are starting from a builder's toolkit plus a year of new math, which is a much friendlier place to begin than the marketing implies. That realisation kept me going more than once, and it is worth holding if you come to this with engineering already under your belt. ## On the hype cycle itself One last thought, because learning to hold this nuance is a skill in its own right. Both extremes are wrong. The "quantum changes everything tomorrow" crowd is wrong, and you should discount them. But the "it is all a scam, nothing works, it is decades of nothing" crowd is equally wrong, and you should discount them too. The truth is the uncomfortable middle: a hard, real, slow moving, genuinely promising technology in an early and limited era, advancing steadily, with the biggest payoffs still years out and not guaranteed. Resisting the pull of both the hype and the backlash, and just looking clearly at what is actually true, is one of the most useful habits you can build in any fast moving field. Quantum is just where I happen to be practising it. ## A quick test before you move on Close this and answer in your own words. What do the three parts of NISQ each mean, and what does the phrase tell you about where the field is? If you cannot unpack noisy, intermediate scale, and quantum, reread the first section, because it anchors everything. Someone tells you quantum computers will break encryption this year. What is your honest, calibrated response? If you cannot explain the gap between today's hundreds of noisy qubits and the million high quality ones it would take, revisit the limits section. And what is the strongest honest reason to start learning quantum now, given that useful hardware is years away? If your answer is not something like the learning curve is long and depth is rare, reread the section on entering early, because that is the actual thesis. ## Where I am learning the honest picture Free, and worth seeking out the calibrated sources specifically. The term NISQ comes from a [well known paper by John Preskill](https://quantum-journal.org/papers/q-2018-08-06-79/) that is still one of the clearest framings of the era, and it is worth reading the source rather than the summaries. For the current state, I follow the research and the company roadmaps but read them sceptically, treating roadmaps as direction rather than schedule. The most useful calibration, though, came from doing the work myself, running circuits on real hardware and seeing the noise, because nothing deflates hype like watching a two gate circuit leak just over one percent of its answers. Experience is the best filter against marketing. ## Why I am here, honestly I am not learning quantum computing because it will make me rich next year. It almost certainly will not. I am not here because it will change the world by Tuesday, because it will not, and I have stopped finding that disappointing. I am here because it is a hard, real, genuinely interesting thing, sitting in an early and honest era, and understanding it deeply, now, while that understanding is still rare, is a bet I am happy to make regardless of when the hardware arrives. No hype required. The truth is more interesting than the marketing anyway, and a great deal more durable. The field does not need me to believe the breathless version. It just needs me to keep doing the math, run the circuits, watch the noise, and tell you honestly what I find. That is the whole job, and it is enough. ### Why your qubits forget: decoherence in plain words URL: https://tuguidragos.com/why-your-qubits-forget-decoherence-in-plain-words/ Last updated: 2026-10-03T13:35:13.000Z In [the last post](https://tuguidragos.com/i-ran-a-bell-state-on-real-ibm-quantum-hardware/), just over one percent of my shots came back as outcomes that were supposed to be impossible, and I blamed three culprits. Gate errors, readout errors, and a third one I waved at and promised to come back to: decoherence. The qubits forget. They do not hold their state while you compute, and on a long enough circuit, that forgetting is what kills you. This is the post where I come back to it, because decoherence is the deepest reason today's quantum computers are hard, and it is usually buried under intimidating jargon. T1, T2, dephasing, relaxation. It is simpler than it sounds. A qubit is a delicate thing trying to hold information in a noisy universe, and it loses. Here is how, in plain words, with the two numbers that measure the loss. ## A qubit is not a sealed box The mental model that fixed this for me: a classical bit is rugged. It is a voltage, high or low, sitting in a chip designed to keep it exactly where you put it, indefinitely. You can set a bit and walk away for a year. A qubit is the opposite of rugged. It is a fragile quantum state, a particular superposition with a particular phase, held in a single physical system that the rest of the universe keeps poking at. Every stray bit of heat, every vibration, every faint electromagnetic whisper from the environment nudges it. And those nudges leak the qubit's information out into the surroundings, where you can never get it back. That leaking is decoherence. The qubit starts in the crisp state you prepared, and over time it blurs into its environment, until the careful superposition you built is just gone, replaced by noise. The qubit has forgotten what it was. The disturbances are not exotic, which is part of what makes them so hard to escape: stray heat, faint magnetic fields, tiny vibrations through the apparatus, even the control electronics you use to manipulate the qubit can jostle it. Anything that carries energy and can couple to the qubit is a potential source of forgetting, and the universe is full of such things. And this is not a manufacturing defect that better engineering fully removes. It is the fundamental tension at the heart of quantum hardware: to compute with a qubit you must touch it, but anything that can touch it can also disturb it, including the universe. There are two distinct ways a qubit forgets, and each has a number. ## T1: the qubit falls down The first way is the simplest. A qubit in the excited state, |1⟩, is holding energy, and energy likes to escape. Left alone, the qubit tends to relax back down to |0⟩, the ground state, dumping its energy into the environment, the same way a hot cup of coffee cools toward room temperature. This is called relaxation, and the number that measures it is T1. T1 is a time. It is, roughly, how long the qubit takes to lose its energy, and the decay is exponential. The probability that an excited qubit is still excited after a time t follows e to the minus t over T1. ```python import numpy as np T1 = 100e-6 # 100 microseconds, a plausible figure for today's hardware for t_us in [0, 10, 50, 100, 200]: t = t_us * 1e-6 print(f"t = {t_us:3d} us P(still excited) = {np.exp(-t/T1):.3f}") ``` Run it. At time zero the qubit is certainly still up, probability one. After fifty microseconds it is down to about sixty percent. After one hundred microseconds, exactly one T1, it has dropped to 0.368, which is one over e, the defining mark of exponential decay. After two hundred it is down near thirteen percent. The qubit is sliding down toward |0⟩ the whole time, and T1 tells you how fast. A bigger T1 is better, because it means the qubit holds its energy longer before falling. On current machines T1 is measured in tens to a couple of hundred microseconds, which sounds like a comfortable amount of time until you realise how long a circuit actually takes. ## T2: the qubit loses its phase The second way is subtler and, for quantum, often worse. Remember from [the phase post](https://tuguidragos.com/relative-phase-the-invisible-engine-of-quantum/) that the interesting information in a qubit lives in its relative phase, the angle between the parts of a superposition. T2 measures how long that phase survives. This kind of forgetting is called dephasing, and it does not require the qubit to lose any energy at all. It just requires the phase to drift. The environment nudges the qubit's phase a little this way, a little that way, randomly, and over time the precise angle you set blurs into noise. The magnitude can be perfectly intact, the qubit still equally |0⟩ and |1⟩, but the relative phase, the thing the next gate was supposed to read, is scrambled. And since the entire computation lives in those phases, as the phase post argued, losing the phase is losing the calculation. T2 is the time over which a superposition stays coherent, holding its phase, before the environment washes it out. Here is the part that matters most. T2 is usually shorter than T1, sometimes much shorter, and it can never be more than twice T1\. So phase, the very thing quantum computing runs on, is typically the first thing to go. The qubit can still be holding its energy, not yet fallen to |0⟩, while the delicate phase relationship that made it useful has already dissolved. That is why dephasing is the quieter, deadlier problem. It destroys the resource you actually compute with, and it does it faster. ## What these numbers look like on real hardware To make this concrete instead of abstract, today's superconducting qubits, tiny electrical circuits that only work when very cold, have T1 and T2 values measured in the tens to low hundreds of microseconds. Good devices push past a hundred, the very best further, but that is the ballpark, and crucially T2 usually trails T1\. These are not secret. When you connect to a real backend, its reported T1 and T2 for every qubit sit right there in the device properties, alongside the gate and readout error rates from [the hardware post](https://tuguidragos.com/i-ran-a-bell-state-on-real-ibm-quantum-hardware/). Reading them is the first thing a thoughtful person does before running, because they tell you your time budget. And you can turn that budget into a gate count. If each layer of gates takes some time, and your phase survives for about T2, then you can fit roughly T2 divided by the gate time worth of layers before the phase is mostly gone. ```python import numpy as np T2 = 70e-6 # 70 microseconds, the dephasing time gate_time = 0.2e-6 # time per gate layer, illustrative layers = T2 / gate_time print(f"layers until coherence drops to ~37%: {layers:.0f}") # ~350 for depth in [50, 200, 350, 700]: coherence = np.exp(-(depth * gate_time) / T2) print(f"depth {depth:4d} -> phase coherence {coherence:.3f}") ``` Run it. With these illustrative numbers you get roughly three hundred and fifty layers before the phase coherence falls to that telltale 0.368, one over e. A shallow circuit of fifty layers keeps most of its phase, around eighty seven percent. By three hundred and fifty layers you are at the one over e mark, more than sixty percent of your phase gone. Past that the circuit is mostly noise. This single calculation, your device's T2 divided by its gate time, gives you a hard ceiling on useful circuit depth, and it is why the algorithm posts later will care so much about keeping gate counts down. The budget is real, it is a number, and you can compute it before you waste a run. ## Why these machines live in the cold This is also why, if you have seen a photo of a quantum computer, it is that gold chandelier hanging inside a tank. That tank is a dilution refrigerator, and the qubits sit at the very bottom, cooled to around a hundredth of a degree above absolute zero, colder than the depths of space. The cold is not decoration and it is not about speed. It is a direct weapon against decoherence. Heat is motion, and motion is exactly the kind of random environmental nudging that scrambles a qubit's state and phase. The warmer the surroundings, the faster the qubit forgets, because there is more thermal energy buzzing around to disturb it. Cooling the chip to nearly absolute zero quiets that thermal noise to almost nothing, which is what buys those tens to hundreds of microseconds of coherence in the first place. At room temperature the qubits would forget so fast you could not do anything with them at all, and the superconducting circuits they are built from would not superconduct in the first place. So the absurd refrigeration is the price of admission: to get even the short coherence times today's hardware has, you must first take the qubits to one of the coldest places in the known universe and hold them there. The cold does not make the qubits good. It makes them barely good enough to compute with before they forget, and that is the whole reason the machine looks like science fiction. ## Why this makes depth the enemy Now connect it to the thing that has been lurking since [the pipeline post](https://tuguidragos.com/running-a-quantum-job-like-an-automation-pipeline/). Every gate takes time to execute. A real gate is a physical pulse lasting some tens or hundreds of nanoseconds, and a circuit is a sequence of these, layer after layer. So a deeper circuit takes longer to run, and the longer it runs, the more T1 and T2 have eaten into your qubits before you ever measure. Depth is time, and time is decoherence. ```python import numpy as np gate_time = 0.2e-6 # 200 nanoseconds per layer, illustrative T1 = 100e-6 for depth in [1, 10, 100, 500]: elapsed = depth * gate_time print(f"depth {depth:4d} -> {elapsed*1e6:5.1f} us elapsed -> survival {np.exp(-elapsed/T1):.3f}") ``` Run it. A circuit one layer deep finishes almost instantly and the qubits barely decay, survival near one. A hundred layers takes twenty microseconds and you are already losing a meaningful chunk. Five hundred layers, at this gate speed, takes a full T1, and your qubits have decayed to that same 0.368, more than sixty percent of the information gone before you read the answer. This is exactly why the Bell state from last post, just two gates, came back mostly intact, while a deep circuit on the same machine would come back as mush. The math does not care how deep your circuit is. The physics cares enormously, because every layer is more time on the decay curve. Shallow circuits finish before the qubits forget. Deep ones do not. ## So what do you actually do You design around it, the same way you design around any unforgiving constraint. You keep circuits shallow, minimising the number of gate layers so the whole thing finishes well inside the coherence time. You favour fewer two qubit gates, since those are both the noisiest and often the slowest. You let the transpiler optimise depth for you, which is part of what it is doing when it rewrites your circuit. And the whole research frontier of error correction exists precisely to fight this: to build a logical qubit that stays coherent far longer than any of its physical parts, by spreading the information across many qubits so that the environment cannot corrupt it faster than the system can repair it. That last idea is worth a moment, because it is the long game the whole field is betting on. The trick of error correction is that you never rely on a single fragile qubit to remember anything. You encode one robust logical qubit across many noisy physical ones, in a clever pattern, and you keep checking the pattern for damage and repairing it on the fly, faster than the decoherence can accumulate. No single physical qubit lasts, but the logical qubit they collectively represent can, in principle, last indefinitely, the way a message protected by enough redundancy survives a noisy channel. The catch is the cost: it can take many physical qubits, sometimes a great many, to protect one logical qubit well, which is exactly why the qubit counts on today's machines, in the hundreds, are nowhere near enough for large scale error corrected computation. The next post takes up that honest accounting. For now, the working rule is simple and physical. Your qubits are on a timer from the moment you prepare them. Finish before it runs out. ## A quick test before you move on Close this and answer in your own words. What is the difference between T1 and T2, in plain terms? If you cannot say something like T1 is the qubit falling from |1⟩ to |0⟩ and T2 is the phase blurring away, reread the two sections, because that distinction is the heart of this. Why is dephasing, T2, often the more dangerous of the two for quantum computation? If your answer does not mention that the computation lives in the relative phase, revisit the phase post and the T2 section together. And why does a deeper circuit suffer more from decoherence? If more gates means more time means more decay is not roughly your answer, reread the section on depth. ## Where I am learning it Free. The physics of T1 and T2 is covered well in the IBM Quantum learning materials and in any solid quantum hardware introduction, and the exponential decay is one of those ideas that becomes obvious the moment you plot it, which is why the few lines above were worth more to me than paragraphs of description. If you have access to a real backend, its reported T1 and T2 values are right there in the device properties, and comparing them to your circuit's runtime turns this whole post from abstract into a number you can act on. As always, I keep the decay curves as NumPy I can rerun, because forgetting that I have only read about does not land like forgetting I have watched fall off a curve. ## The fragile thing in the cold A classical bit will sit in a chip for years without complaint. A qubit, by its nature, cannot. It is a delicate quantum state held in a real physical object that the universe will not stop disturbing, and from the moment you create it, it is already beginning to forget. T1 measures how fast it falls, T2 how fast it loses the phase, and a circuit is a race to finish before both numbers run out. That fragility is not a flaw someone forgot to fix. It is the deep cost of working with quantum information at all, the reason these machines live in dilution refrigerators colder than deep space, the reason depth is dangerous, and the reason error correction is the whole ballgame for the next decade. Your qubits forget. Understanding exactly how is the difference between being surprised by the noise and designing around it. ### Qiskit Global Summer School 2026: 4 labs, 473 seconds of real quantum time, 1 badge URL: https://tuguidragos.com/qiskit-global-summer-school-2026-quantum-excellence-badge/ Last updated: 2026-09-13T17:34:55.000Z At 20:43 on Friday 24 July, 1 Estimator call on ibm\_marrakesh consumed 264 seconds of real quantum processor time. That is 44% of everything the free IBM plan gives you in a month, spent on 1 cell of 1 notebook, 5 days before a deadline. The notebook offered a simulator instead. I ran it on the machine, because the lesson was on the machine. That job is 1 line in the logbook of the Qiskit Global Summer School 2026, and the logbook is why this post exists, more than the [Quantum Excellence badge](https://www.credly.com/badges/a1449ad9-7e69-46f8-b7f4-b5b9d0f3da42/public%5Furl) IBM issued for it on 21 August. The badge is my 6th IBM Quantum credential. The 5 before it, from the [first](https://tuguidragos.com/i-earned-my-first-ibm-quantum-badge/) in May to the [general formulation badge](https://tuguidragos.com/the-density-matrix-what-the-state-vector-misses/) in June, were courses, some with a 20 question exam at the end, and they attest to understanding. I was careful to say so each time. This one is different. It came from 2 weeks of lectures and 4 graded labs, each a Jupyter notebook that hands you a half written function and a grader that inspects the object your code produces. In the last 2 labs, the object came back from a real quantum computer. You do not pass by knowing the answer. You pass by making the answer run. And there was a deadline, 29 July at noon Eastern, with work on my side of the Atlantic that did not care about it. I am a self taught automation builder. No physics degree, no computer science degree, in quantum since 1 paragraph about Grover's algorithm caught me in the spring. A program with a grader and a clock is the kind of thing the version of me from a year ago assumed was for other people. So instead of remembering the 2 weeks, I pulled the list. ## The logbook, from my own job history What follows is not memory. It is the job list from my IBM Quantum account, exported and converted to my time zone. Every hardware run leaves a record: a job ID, a timestamp, a backend, a program type, the seconds of QPU time it consumed, and the tags you attached. Most of the lab notebooks tagged their jobs `qgss26`, and the 2 runs that went out untagged are easy to pick out by their circuits. My IBM Cloud instance was created on 22 June. My [first job ever on a real machine](https://tuguidragos.com/quantum-circuits-ibm-hardware-n8n/) ran on 24 June, on ibm\_kingston, at 21:39\. Before the first job with the school's tag on it, I had 15 hardware jobs to my name. Everything ran on the free Open Plan of the IBM Quantum Platform, which gives you 10 minutes of real QPU time per 28 day window. Here is every run that belongs to the summer school, in German time, which is the time zone I was sitting in when I pressed run. The first line is Lab 0, the ungraded setup notebook released before the school opened, which ends with an optional run on a real machine. The last column is what the notebook said the cell would cost before you ran it. | When, Berlin time | Backend | What | QPU time | Notebook estimate | | -------------------------- | -------------- | ---------------------------------------------- | -------- | ------------------------ | | Thu 9 Jul, 21:26 | ibm\_fez | Lab 0, Sampler and Estimator | 14 s | about 20 s | | Thu 16 Jul, 22:00 | ibm\_marrakesh | Lab 2 bonus, 12 qubit dynamic GHZ | 3 s | | | Mon 20 Jul, 21:20 to 21:37 | ibm\_marrakesh | Lab 3, 6 noise learner and 4 executor jobs | 80 s | 1 min 30 s | | Fri 24 Jul, 14:57 | ibm\_marrakesh | Lab 4b, Sampler, no mitigation | 2 s | 5 min 30 s for all of 4b | | Fri 24 Jul, 20:21 to 20:43 | ibm\_marrakesh | Lab 4b, 7 more jobs, PEC included | 321 s | | | Fri 24 Jul, 20:54 | ibm\_marrakesh | Lab 4b bonus, 1 Estimator run | 50 s | 38 s | | Sat 25 Jul, 05:48 | ibm\_kingston | Lab 4c, LUCJ circuit for the nitrogen molecule | 3 s | 2 s | | Total | | | 473 s | | 473 seconds. Under 8 minutes of QPU time, spread over 4 evenings, 1 afternoon and 1 dawn, out of the 10 minutes the plan gives you. The estimates were honest, sometimes to the second: Lab 3 said 1 min 30 s and took 80 seconds, Lab 4b said 5 min 30 s and took 5 min 23 s without the bonus, the 4c sampling said 2 seconds and took 3\. So the school quietly teaches you to budget a scarce resource, to know before you press run how much you are about to spend, and to retrieve a finished job by its ID instead of rerunning it when your kernel dies. Those are the skills I already had from [running pipelines](https://tuguidragos.com/running-a-quantum-job-like-an-automation-pipeline/) that cost money, and it was the first time I saw them written into a physics course as a first class concern. 2 things in that table are worth pointing at. The first is that the Lab 3 jobs are stamped 21:20 on Monday 20 July, and the Lab 3 notebook landed on [GitHub](https://github.com/qiskit-community/qgss-2026) that same afternoon at 17:36\. I had it running on a real chip less than 4 hours after it existed. The second is the last line, and I will get to it. ## What the Qiskit Global Summer School 2026 actually was The Qiskit Global Summer School is IBM's free, online, 2 week program on quantum computing with Qiskit. The 2026 edition was its 7th year, and it ran from 13 to 24 July under the theme [A decade on the cloud](https://www.ibm.com/quantum/blog/qiskit-summer-school-2026), because on 4 May 2016 IBM [put a 5 qubit processor on the internet](https://www.ibm.com/quantum/blog/decade-of-quantum) and let anyone run circuits on it. The [2016 announcement](https://www.prnewswire.com/news-releases/ibm-makes-quantum-computing-available-on-ibm-cloud-to-accelerate-innovation-300262512.html) predicted machines of 50 to 100 qubits within 10 years. The machines in my table have 156. IBM's attendee guide said about 12,000 people registered, and IBM Quantum's own [wrap up post](https://www.linkedin.com/posts/ibm-quantum%5Fqiskit-global-summer-school-2026-global-traffic-activity-7490390806306959360-j4P5) counted more than 5,000 attendees from 95 countries and nearly 1,900 cities. Registration opened on 26 May and was closed by 29 June. If you are thinking about 2027, that is the part to plan for. The labs give you 2 weeks. The registration window gave 5, and it did not wait. The shape of a day was nearly fixed: a core lecture by an IBM Quantum researcher at 8:00 in the morning Eastern time, a live Q&A with that lecturer at 10:00, and around midday a lab release, a workshop or a guest lecture. 10 core lectures over the 2 weeks by IBM Quantum researchers, from Roland de Putter's opening on why quantum at all to a 4 part algorithms series that ends with Sabina Dragoi on optimisation. 4 distinguished speakers on top: Ignacio Cirac, Kenneth Merz and Fangchun Liang from the Cleveland Clinic, and Aram Harrow. And 4 optional workshops, one of them titled "Why Does Quantum Computing Seem Scary?". I live in Germany. 8:00 Eastern is 14:00 here, the middle of a working day, so I watched every lecture recorded, at my own pace. The rules were explicit. The platform tracks how much of each video you actually watch, and to qualify for anything you needed 80% of all core lectures viewed. From there, a Certificate of Completion needed nothing else, the Quantum Fundamentals badge needed 1 or 2 labs completed, and the Quantum Excellence badge needed 3 or 4\. Guest lectures, panels and workshops counted for nothing. Labs could be resubmitted as many times as you wanted, and only the highest score counted. The 80% is a watch time counter, and I know the difference between a counter and understanding. The labs were where the understanding got checked. ## Lab 1: circuits for real hardware, in 2 languages Lab 1, written by James Weaver, is about building circuits that survive contact with a real processor. It starts with X, H and CNOT and the Bell state, and turns into an engineering lab about depth. A GHZ state on N qubits is trivially a Hadamard followed by a chain of CNOTs, N layers deep, and depth is what costs you most on real hardware, because every layer is time and time is decoherence. So you fan out from the middle, then recursively, and the exercise asks for a 16 qubit GHZ state in depth 5\. Then transpilation. Heron processors, the IBM generation every machine in my table belongs to, do not run H or CNOT natively. Their native gate set is RZ, SX, X and CZ, where SX is √X, and the transpiler rewrites everything else into those, on a heavy hex lattice where each qubit has 2 or 3 neighbours. A CNOT between qubits that are not neighbours costs SWAPs at 3 entangling gates each. The final challenge is a 64 qubit GHZ state on a simulated 133 qubit Heron, built from a breadth first search over the coupling map from qubit 25 and entangled along the resulting tree layer by layer. New this year, Lab 1 also came in C++, written against the Qiskit C API. You only had to solve 1 version. I did both, in VS Code, on my own laptop, in an environment I had learned to pin [the hard way](https://tuguidragos.com/the-version-trap-that-cost-me-an-evening-qiskit-2-x/) 2 days before the school opened. ## Lab 2: noise, backends, and 1 dynamic circuit on a real chip Lab 2, by Alberto Maldonado Romo and Gwonhak Lee, is where the circuit model meets calibration data. You load the backend object for ibm\_fez and read what defines a machine: native gates, coupling map, gate errors, readout errors, and T1 and T2, the 2 clocks that say how long a qubit keeps its energy and its phase. You build 3 toy noise models by hand, depolarising, Pauli and thermal relaxation, compare Heron's heavy hex lattice with Nighthawk, IBM's newer square lattice processor, by transpiling a 120 qubit GHZ circuit onto both, and finish with dynamic circuits, where a measurement in the middle of the circuit decides the next gate. The bonus is the second line in my table: a 12 qubit dynamic GHZ on ibm\_marrakesh, Thursday 16 July at 22:00, 3 seconds. The notebook says the gap between the simulator's prediction and the real result is crosstalk, leakage, drift and the overhead of the classical feedforward, and that narrowing that gap is one of the central goals of error mitigation research. Which is Lab 3. ## Lab 3: the one that cannot run on a simulator Lab 3, by Sophy Shin and Sophie Engineer, is titled "Your New Tool For Quantum Advantage", and it opens with a sentence I had not seen in a course before: this lab requires execution on real hardware, and it is not possible to run it on a simulator. It is about learning the noise of a specific device and then using what you learned, and a simulator has no noise of its own to learn. It starts with what the Qiskit Runtime already gives you as switches on the Estimator. Dynamical decoupling and Pauli twirling, which suppress errors before they land. TREX, ZNE, PEA and PEC, four error mitigation methods, which clean up afterwards, from correcting the readout to learning the noise and sampling circuits that invert it. The switches are whole circuit policies. Twirl every 2 qubit gate, decouple every idle window. They cannot treat one layer differently from another, and they cannot show you the noise they learned. 6 weeks later, in the [Bell state post](https://tuguidragos.com/i-ran-a-bell-state-on-real-ibm-quantum-hardware/), where I ran the simplest entangled circuit on a real chip and accounted for every wrong answer, I switched 2 of them on and watched them do nothing. This lab had already explained why. Then it hands you the tool that can. Samplomatic, a Qiskit library, lets you box up a slice of a circuit and annotate the box: `Twirl`, `InjectNoise`, `ChangeBasis`. `NoiseLearnerV3` runs jobs on the real device to learn the Pauli noise of each entangling layer, and the `Executor` primitive runs the boxed program. The worked example is a 1D Ising chain, a line of spins each coupled to its neighbours, evolved with a Trotter circuit, the standard way of approximating time evolution with gates, and run forward and backward, so the ideal answer is known and the whole deviation is noise. On top sit 2 add ons: one rewrites the observable to absorb the learned noise, the other, shaded lightcones, cuts the sampling cost of PEC down to the part of the circuit an observable can actually see. I ran them on 10 and 15 qubits. The overhead is the point of the whole lab. Error mitigation does not cost you qubits. It costs you shots, and for probabilistic methods that cost grows exponentially, with a base set by the learned noise. A lower gate error shrinks the base, and because the base is raised to the size of the circuit, a small improvement in hardware collapses the bill. I had read that sentence before. Lab 3 made me pay for it: 10 jobs on ibm\_marrakesh between 21:20 and 21:37 on 20 July, the evening the lab was released, 80 seconds of QPU time. A month later, on 21 August, the day the badge was issued, my [n8n node for IBM Quantum](https://tuguidragos.com/ibm-quantum-error-code-1506-1603/), the connector I maintain so that an automation workflow can submit circuits to IBM machines, was submitting noise learner jobs of its own. ## Lab 4: towards quantum advantage, and the night I did not sleep Lab 4 is 3 notebooks, written by Jorge Martínez de Lejarza and Boseong Kim, titled "Towards quantum advantage". It landed on GitHub on Thursday 23 July, 6 days before the deadline. It is the one I want to be most precise about. Lab 4a is reading. It sends you to the paper ["A Framework for Quantum Advantage"](https://arxiv.org/abs/2506.20658) by Lanes and colleagues for the 2 criteria a computation must satisfy before anyone gets to use the phrase, then to the [Quantum Advantage Tracker](https://quantum-advantage-tracker.github.io/), the community run site that documents claimed demonstrations under 3 pathways, to look up 2 specific numbers: the lowest energy reported for an iron sulfur cluster with a particular method, and a Loschmidt echo on 49 qubits and 648 gates. It is the first time a lab made me read the primary literature to pass a grader. Lab 4b is QAOA, the Quantum Approximate Optimization Algorithm, on the partition problem: split a list of numbers into 2 groups whose sums are as close as possible. The notebook turns a 6 number grocery example into a cost Hamiltonian whose couplings are the products of pairs of numbers, hands you a QAOA circuit with pretrained parameters, and sends it to the machine through the Sampler at 1,000 shots, 2 times: once with no mitigation, once with dynamical decoupling + twirling. 2 more jobs that Friday evening are tagged "M3 calibration", 8 circuits each at 4,096 shots, the runs mthree fires to correct readout errors without ever building the full 2ⁿ × 2ⁿ assignment matrix. Then it scales. A 160 number problem needs 160 qubits the naive way. Pauli Correlation Encoding stores the variables in the correlations between pairs of qubits instead of in the qubits themselves, and the count comes out at 11\. Exercise 4 sends the encoded problem through the Estimator in 4 runs with increasing ambition: nothing, TREX with twirling, ZNE with twirling, and PEC with twirling. The notebook warns you before the last one: 4 min 26 s of estimated QPU time for a single cell, and if you are not comfortable with that, run it on a simulator instead and lose the lesson. I ran it on the machine. The PEC job on ibm\_marrakesh consumed 264 seconds, 4 min 24 s, within 2 seconds of the estimate. 44% of the plan's allowance, on 1 Estimator call, on an 11 qubit circuit. The lecture on sampling overhead had a number in it now. Then there was the bonus, and the bonus is where the lab held me. 10× larger: 1,600 numbers, which under the same encoding is a 34 qubit circuit. Not required for the badge. Scored from 0 to 100 on how small a difference you achieve between the 2 partitions, and only results from real hardware count. The recommended path trains the QAOA parameters on hardware in a session, about 32 seconds per iteration, 16 minutes for 30 iterations, 35 minutes estimated for the cell. By the free plan's arithmetic, 432 of the window's 600 seconds were already gone at that point in the evening: the 420 in the table and a 12 second job from 1 July that had nothing to do with the school. That left 168 seconds. So I took the path the notebook offers for people like me: skip the hardware training, measure the expectation value on hardware with untrained parameters, then do the rest with your head, a custom error mitigation configuration and a local search that flips single bits around the best solution decoded from the quantum run, to repair the flips the noise put there. My last Estimator job of the night went out at 20:54 with readout mitigation, zero noise extrapolation and twirling all switched on, 4,096 shots, 50 seconds of QPU time. And then the post processing, which is classical. Whatever you think of quantum computers, the honest description of this bonus is a quantum estimate that a classical search then has to rescue, and getting the rescue right on a 34 qubit encoding of 1,600 numbers was the hardest thing in the 2 weeks. Not the physics. The engineering around the physics. One participant wrote under IBM's wrap up post, "When I first opened lab 4, I was scared." I read that after the school. The timestamps in my table are my version of the same sentence. I did not stop when the bonus was done. Lab 4c was the third notebook, sample based quantum diagonalisation on the nitrogen molecule, marked at the top as a bonus lab. 26 active orbitals, 5 electrons of each spin, 65,780 configurations per spin sector, a Hilbert space of over 4 billion states. You seed a LUCJ ansatz, a local unitary cluster Jastrow circuit, from a classical coupled cluster calculation, sample bitstrings from the hardware, recover the ones that violate electron number, and hand the survivors to a classical solver that diagonalises the Hamiltonian inside that small subspace. The quantum computer proposes which electron arrangements matter. The classical computer does the linear algebra. It is the hybrid loop from my [variational badge](https://tuguidragos.com/i-earned-an-advanced-ibm-quantum-badge/), and the third of the [3 ways to diagonalise a giant matrix](https://tuguidragos.com/three-quantum-ways-to-diagonalize-a-giant-matrix/) I wrote up in June, pointed at chemistry. The sampling job for the LUCJ circuit ran on ibm\_kingston at 05:48 on Saturday morning. 3 seconds of QPU time. I had not slept. I went from the Friday evening jobs straight through the bonus and into 4c and did not stop until every exercise in it had passed, including the ones that were never going to count. I am not recommending it. I am recording it, because this is a logbook, and the timestamp is real. The deadline was Wednesday 29 July at noon Eastern. The table has nothing after 05:48 on Saturday, 4 days before it. ## 3 things the lectures left behind I will not summarise 10 lectures. These stayed. Thaddeus Pellegrini's lecture on entanglement spent a whole section on what entanglement does not mean, including the assumption that it automatically delivers a speedup. 6 weeks later, in the Bell state post, I wrote a caution of my own about what a histogram does not prove. Different claim, same instinct, and the lecture got there first. Vincent Pascuzzi's lecture on quantum + HPC made the argument I have been making to myself since June: the credible claim is not that quantum computers replace supercomputers. It is a CPU, a GPU and a QPU in 1 workflow, each doing the part it is good at. That is the shape of every system I have ever wired. And Minh Tran's lecture on tracking advantage had a line worth keeping: quantum advantage is not a destination. 6 days after the school closed, on 30 July, IBM and its partners [announced 3 verified quantum advantage demonstrations](https://www.ibm.com/quantum/blog/quantum-advantage). The tracker that Lab 4a had made me learn to read was suddenly the place to read them. ## What the Quantum Excellence badge is, and what it is not The rules say Quantum Excellence is 80% of the core lectures + 3 or 4 labs completed. I did all 4 at full score on every graded exercise, and the 4b bonus and the whole of 4c on top. That is what I did and I am putting it on the record the way I put everything here, dated, with the job history behind it. What it is not is a research result. The grader checks that the object your code produced is the object it expected, the same idea I later [built 20 exercises around](https://tuguidragos.com/i-built-a-place-to-practise-qiskit/). It does not check whether you understood why. That part is on you, because the alternative is a badge that knows more than you do. It is not a claim that error mitigation works at scale. I watched PEC cost 264 seconds on 11 qubits, and the lab itself shows the exponent that number lives under. And it is not a certificate of hardware mastery. The machines were noisy on 24 July just as they were noisy on 30 August, when I ran the Bell state, and the honest state of the field is the one I wrote about after that run, not a softer one. ## Why this one matters to me Before the school I had run 15 jobs on real hardware, most of them in 1 evening in June. By 10 September the count was 327, across ibm\_fez, ibm\_marrakesh and ibm\_kingston: the [n8n node](https://tuguidragos.com/six-defects-verified-n8n-node-ibm-quantum/) being tested against real machines, the [Bell state](https://tuguidragos.com/i-ran-a-bell-state-on-real-ibm-quantum-hardware/) with its 50 impossible outcomes, the 2 [error codes](https://tuguidragos.com/ibm-quantum-error-code-1506-1603/) I had to catalogue because they kept coming back. The summer school sits between those 2 numbers. To me this badge is the bridge to real hardware, and the job list is the evidence. I came into quantum through the side door, from automation. The school turned out to be 2 weeks of budgets, retries, job IDs and knowing what a tool is for before switching it on, and the hardest exercise in it was a classical search. That is the door I came through. The badge is dated 21 August 2026\. The email arrived on 11 September. The last job in its logbook is dated 05:48 on a Saturday. All 3 of those are true, and I would not trade any of them. > 5 badges said I understood. This one says the circuits ran. I know which of the 2 I trust more. ### I ran a Bell state on real IBM quantum hardware URL: https://tuguidragos.com/i-ran-a-bell-state-on-real-ibm-quantum-hardware/ Last updated: 2026-09-13T18:42:33.000Z Most of the code in this series so far ran on a simulator. A perfect, noiseless, mathematical model of a quantum computer, living on my own machine, where the answer comes out exactly as the math says it should. That was the right place to learn. It is not where quantum computers actually are. This time I did something different. I took the simplest entangled circuit, the Bell state from a few posts back, and I ran it on a real quantum computer. An actual chip, cooled to near absolute zero, sitting in an IBM lab, reached over the internet. The result was not perfect. Then I did the part that most first hardware posts skip: I opened the machine's own calibration data and worked out where every wrong answer came from. ## The circuit, and the perfect answer Here is the circuit. It is the same two gates as [the entanglement post](https://tuguidragos.com/entanglement-explained-without-the-spooky/): a Hadamard, then a CNOT, then measure both qubits. ```python from qiskit import QuantumCircuit from qiskit.primitives import StatevectorSampler qc = QuantumCircuit(2, 2) qc.h(0); qc.cx(0, 1) qc.measure([0, 1], [0, 1]) ideal = StatevectorSampler().run([qc], shots=4096).result()[0].data.c.get_counts() print(ideal) # only '00' and '11', about 2048 each ``` Run it. On the simulator you get only 00 and 11, split roughly evenly, and nothing else. The two qubits always agree, exactly as the Bell state promises. Zero counts on 01 and 10, the outcomes where the qubits disagree, because in the ideal state those outcomes have zero amplitude. Hold this clean result in mind. It is the reference the real machine is about to be measured against. ## Getting onto a real machine IBM gives free access to real quantum hardware over the cloud, through the Qiskit Runtime. The flow is [the pipeline from the earlier post](https://tuguidragos.com/running-a-quantum-job-like-an-automation-pipeline/), with the target swapped from simulator to a physical device. You connect to the service, pick a backend, transpile your circuit for that specific machine's native gates and layout, submit the job, and wait in a queue behind everyone else on Earth doing the same thing. Then the results come back. I will not paste my credentials and connection boilerplate here, because that part is just setup and [the IBM docs](https://quantum.cloud.ibm.com/docs/en/guides) cover it better than I could. The conceptual shift is the only thing that matters: this is the pipeline going to production. The simulator was your dev environment, free and instant and perfect. The real device is prod, with a queue, a cost in wait time, and output that reflects the messy physics of an actual machine. The code shape barely changes. What changes is that the universe is now doing the computing, and the universe is noisy. ## The connection, in shape Even though I will skip my account specifics, the shape of the code is worth seeing once, because it is almost identical to the local pipeline. You connect to the runtime service, ask it for a real backend, compile your circuit specifically for that machine, and run it through a Sampler, exactly the same primitive as before, just pointed at hardware instead of a simulator. ```python # requires the qiskit-ibm-runtime package and your own IBM Quantum account from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager service = QiskitRuntimeService() # saved credentials backend = service.least_busy(operational=True, simulator=False) pm = generate_preset_pass_manager(target=backend.target, optimization_level=1) isa = pm.run(qc) # compile for THIS machine sampler = SamplerV2(mode=backend) job = sampler.run([isa], shots=4096) counts = job.result()[0].data.c.get_counts() # after the queue, real data ``` That is the whole structure, and notice how little is new. The service and backend lines are the only genuinely hardware specific part, and they are just "log in and pick a machine." The compilation step is the transpile step from the pipeline post, except now it targets a real device's gate set and qubit layout rather than an abstract one, which is why it goes through the backend's own target rather than a hand written gate list. And the Sampler is the same primitive concept, run a list of circuits, get structured results back, just running in the cloud on a physical chip with a queue in front of it. The pipeline shape held. Only the target moved from your laptop to a refrigerator in a lab. ## The real answer, on ibm\_fez on 30 August 2026 The machine I got was ibm\_fez, a 156 qubit Heron r2 processor, last calibrated fifty two minutes before my job ran. The compiler placed my two logical qubits onto physical qubits 23 and 22, and the finished circuit was seven layers deep: five rz rotations, three sx gates, each half of an X gate, two measurements, and exactly one two qubit gate. Here is what came back, out of 4096 shots: ``` 00: 2078 (50.73%) 11: 1968 (48.05%) 01: 26 (0.63%) 10: 24 (0.59%) ``` Look at what happened. The two outcomes that are supposed to dominate still dominate, so the correlation the circuit predicts survived the trip. 00 came back somewhat more often than 11, but before reading anything into that, check it against ordinary sampling noise: the gap is under two standard deviations of what you expect when you flip a fair coin four thousand times. A small part of it is real, for a reason I get to below, but most of it is statistics, and mistaking one for the other is the easiest way to fool yourself on a first hardware run. The number that matters is the other one. Fifty shots came back as 01 or 10\. According to the ideal Bell state those outcomes have zero amplitude and cannot happen, ever, not rarely but never. The qubits disagreed, in a state where they are physically not supposed to be able to disagree. Those fifty counts did not come from the math. They came from the hardware. That little pile in the wrong bins is noise, made visible, and the rest of this post is about reading it rather than shrugging at it. ## Where the wrong answers come from Three physical imperfections produce those stray counts, and it is worth naming them because they are the whole story of why today's hardware is hard. Gate errors. The CNOT on a real device is not the perfect matrix from [the gates post](https://tuguidragos.com/quantum-gates-are-just-matrices-you-can-run-by-hand/). In fact my CNOT never reached the chip as a CNOT at all: the compiler rewrote it into a CZ plus single qubit rotations, because CZ is the two qubit operation this processor physically implements. Whatever it is called, it is a physical operation driven by microwave pulses, and it is slightly off every time. On my run the published error was 0.18 percent for that CZ and 0.02 percent for a single qubit sx, which is the usual ordering: the entangling gate is the expensive one, an order of magnitude worse than its single qubit neighbours. Readout errors. When the machine measures a qubit, it sometimes misreads it, purely because reading a quantum state off real hardware is an imperfect physical measurement. My two qubits had published readout errors of 0.56 and 0.42 percent. And this error is not symmetric, which is worth pausing on. From the device's published readout figures you can build a full assignment matrix, the probability of measuring each outcome given each true state, and mine says a true 00 is misread 0.64 percent of the time while a true 11 is misread 1.42 percent of the time. More than twice as bad. The reason is physical: measurement takes time, and during that time a qubit sitting in the excited state can relax down to the ground state, whereas a qubit already in the ground state has nowhere to fall. Reading a 1 is harder than reading a 0. Decoherence. The qubits do not hold their state perfectly while the circuit runs. They drift, they leak, they forget. My worse qubit had a phase coherence time of 64 microseconds, and the whole circuit took 1.8 microseconds, so it spent under three percent of its coherence window alive. That is why decoherence barely registers here. It is the dominant enemy as circuits grow, and it gets its own post next, because it is the deepest reason depth is dangerous. Every count in the wrong bin is one of these three, in physical form. The noise is not random malice. It is gate imperfection, plus readout imperfection, plus the qubits quietly decaying, all of it leaking a small fraction of your shots into outcomes the ideal math forbids. ## The single qubit runs show the readout asymmetry on their own I had spare shots in the same batch, so I ran three trivial single qubit circuits on qubit 110 as controls, and one pair of them makes the readout asymmetry visible without touching a matrix at all. Prepare the qubit in |0⟩ and measure it 2048 times: 10 shots came back as 1, an error of 0.49 percent. Prepare the same qubit in |1⟩ on the same machine in the same batch and measure it 2048 times: 21 shots came back as 0, an error of 1.03 percent. Same qubit, same measurement, twice the error rate for the excited state, the same asymmetry the assignment matrix predicts, and for exactly the reason above. This is the cheapest experiment in the post and one of the most useful. Two circuits with no gates worth speaking of, and they characterise the measurement itself, separately from anything your algorithm does. When a result looks wrong, knowing your readout's own error rate, per state, tells you how much of the wrongness you should have expected before you start blaming your circuit. ## What this histogram does not prove One honest caveat, because it is the thing I would have got wrong a few months ago. A histogram taken in a single measurement basis does not demonstrate entanglement. It demonstrates correlation. If someone had quietly replaced my quantum computer with a coin flip, preparing 00 half the time and 11 the other half, decided classically before anything was measured, the counts would look exactly the same as mine. Nothing in those four numbers separates a genuinely entangled state from a boring classical mixture. What separates them is phase, and [phase is invisible in this basis](https://tuguidragos.com/relative-phase-the-invisible-engine-of-quantum/). The Bell state holds a definite phase relationship between its two branches that a classical mixture does not, and it shows up the moment you rotate the measurement before reading, or run a Bell inequality test, or reconstruct the state properly with tomography. That is a second experiment, and a post of its own. So the honest reading of my run is this. I built a circuit that should produce entanglement, ran it on real hardware, and confirmed that the correlation it predicts is present, with a noise floor I can account for down to five shots. Confirming the entanglement itself needs more than one basis. Claiming it from counts like these is an easy mistake to make and a very common one to read. ## You can account for most of the noise before you guess The temptation, looking at fifty stray counts, is to shrug and say "hardware is noisy." You can do better than that, with arithmetic, and the machine hands you every input you need. Start with readout. Both correct Bell outcomes have the two qubits agreeing, so if exactly one of them is misread, they no longer agree and the shot lands in 01 or 10\. With published readout errors of 0.56 and 0.42 percent, the chance that at least one of the two is misread on a given shot is 0.97 percent, which over 4096 shots predicts about forty shots in the wrong bins. Then the gate. This one needs a caveat, because the published CZ error is an average infidelity from benchmarking, not a probability of flipping a bit. Turning 0.18 percent of infidelity into an expected number of disagreements assumes a noise model, and the simple depolarising assumption puts the contribution on the order of 0.12 percent, roughly five shots. Treat that as an estimate with a model behind it, not a measured quantity. Add them: about 1.09 percent predicted, and I measured 1.22 percent. The gap between the model and the measurement is 0.13 percent, five shots out of 4096\. A two term back of the envelope, built entirely from numbers the machine published about itself, lands within five counts of what came back. There is a plausible home for those last five shots, and it is worth stating as a hypothesis rather than a result. Almost all of the circuit's 1.8 microseconds is the measurement itself, and relaxation during measurement is already baked into the published readout error, which is precisely why a true 11 is misread more often than a true 00\. Adding a separate decoherence term for that stretch would count the same physics twice. But the gates before the measurement take their own hundred or two hundred nanoseconds, and relaxation across that window, on qubits with these coherence times, works out to roughly a tenth of a percent. That is the order of the residual. I have not proved it is the residual, and one run cannot. One more thing falls out of the assignment matrix. The probability of both qubits being misread on a single shot is about one in ten thousand, which over 4096 shots is a fraction of a single count, so essentially none of my noise is hiding inside 00 and 11 disguised as a correct answer. And the asymmetry from earlier shows up here too: because a true 11 is misread more often than a true 00, and because a misread 11 very occasionally lands on 00 while a misread 00 essentially never lands on 11, the measured 00 bar should sit slightly above the measured 11 bar. It does. That accounts for perhaps fifteen of the hundred and ten count gap between them. The rest really is just sampling noise. That is the difference between watching noise and reading it. Not a clean decomposition, one run cannot prove attribution, and the model is deliberately crude. But going from "there is some noise" to "four fifths of this is the final measurement being misread, on a device whose published rates predict it to within five counts" is a diagnosis rather than a shrug. One caveat, and it is not a small one. Those error rates are the numbers the machine published fifty two minutes before I ran. Calibration drifts. The same qubits next week are not the same qubits numerically, which means the arithmetic above has a shelf life and has to be redone against fresh device properties every time. A number you measured once and wrote down is not a number you still know. ## Correcting for the readout removes most of the error Arithmetic that predicts something should be tested by fixing that something. If four fifths of my wrong answers are the measurement being misread, then undoing the measurement error should remove four fifths of them. The assignment matrix makes this concrete. It tells you the probability of measuring each outcome given each true state, so inverting it takes your observed counts back towards what the device most likely actually prepared. I ran a second baseline, which came back with 1.42 percent of its shots in the wrong bins, and applied the correction. The error dropped to 0.42 percent. About seventy percent of it gone, by post processing alone, no change to the circuit and no extra shots. Two honest notes on that. First, the leftover is still around seventeen shots, roughly three times what the published CZ error on its own predicts, which is the sign that a two term budget is a simplification rather than a complete physical model. Second, this correction is a statistical estimate, not a repair. Nothing on the chip got better. I made a more informed guess about what the chip was trying to tell me, using the chip's own characterisation of how it mishears itself, and that guess can be wrong in ways the raw counts cannot. ## Twirling and dynamical decoupling did nothing here I then ran the same circuit with two techniques switched on, twirling and dynamical decoupling, half expecting an improvement to report. The wrong bins came to 1.51 percent, against 1.22 and 1.42 percent for the two baselines. It got numerically worse. Before concluding anything from that, look at what the baselines are doing. Two identical runs, nine minutes apart, on the same machine and the same qubits, gave fifty and fifty eight wrong shots. Eight counts apart, with nothing changed between them. That spread is my measurement resolution, and the mitigated run sits four counts outside it. The correct statement is not that mitigation made things worse. It is that on this circuit, the effect of these techniques is smaller than my ability to measure it. Which is what you should expect once you think about what each one is actually for. Dynamical decoupling protects qubits that are sitting idle while other parts of a circuit do work. My circuit is seven layers deep with one entangling gate and no idle stretches worth protecting, so there was nothing there for it to fix. Twirling is not even aimed at lowering the error rate in the first place: it converts coherent, structured errors into a well behaved random channel, which is what makes the mitigation layered on top of it statistically valid. Judging it by whether the raw count of wrong shots went down is judging it by the wrong metric entirely. That is the real lesson, and it is why I am writing up a result that improved nothing. Error suppression is not a switch that makes results better. It is a set of tools aimed at specific failure modes, each with its own success criterion, and if your circuit does not exhibit the failure a tool targets, turning it on buys you nothing and costs you overhead. Knowing which tool addresses which failure is the actual skill. The alternative was to run it, see no improvement, quietly leave it out, and write a post implying that switching on mitigation is straightforwardly good. It is not. The whole investigation, three jobs, cost fifteen seconds of QPU time out of the free plan's six hundred seconds per twenty eight day window. The job IDs are daa5tg1qtnsc73d298tg, daa6236rbfbs73cibee0 and daa63eerbfbs73cibfq0, in case anyone wants to check my arithmetic against the record. ## What the error rate is telling you Add up the wrong outcomes. In my first run, 01 and 10 together were 1.22 percent of all shots. That number is a rough health reading on the device and the circuit together: the fraction of results that landed where they physically should not have. For a two gate Bell state on a current Heron processor, on a good day, that is about what you should expect, and it varies with the machine, the qubits you happen to be given, and the hour. It is a number worth computing every time you run, because it calibrates your expectations for everything that comes after. Take that published CZ error of 0.18 percent and ask what it does at depth. One entangling layer leaves you at 99.8 percent. Ten layers, 98 percent. A hundred layers, 83 percent. By around three hundred and seventy six layers you are at fifty percent, a coin flip, and that is counting the two qubit gate error alone, with decoherence and readout still to come on top. At a thousand layers you are at sixteen percent. At five thousand, the number rounds to zero. That is the whole problem in one column of figures. My circuit had one entangling layer and it was fine. The algorithms people are excited about need thousands, and the gap between one and a thousand is not a matter of waiting for a slightly better chip. It is several orders of magnitude in gate fidelity, or error correction, or both. ## Why this is the honest reality of quantum in 2026 This run is a small, concrete window onto the thing the hype usually skips. Real quantum computers today are noisy. Not a little noisy in a way that will be patched next quarter, but noisy in a way that fundamentally limits what you can run, because errors accumulate faster than current machines can correct them. Two gates leaked just over one percent, and four fifths of that was the measurement being misread rather than the computation going wrong. Read quickly, that sounds encouraging, and in one sense it genuinely is: this hardware is considerably better than the hardware of a few years ago, and the dominant error source on my run was the easiest one to correct for. Then look at the depth column again. A per gate error that is almost invisible at depth seven is fatal at depth seven thousand. We are not there, not yet. I am not saying this to be a downer. I am saying it because seeing it yourself is worth more than reading a hundred breathless articles. The next post digs into the physics of why the qubits forget, and the one after that lays out the honest state of the field without the marketing. But the foundation for both is sitting right there in those four numbers: the correlation held, the noise was real, and both facts are true at once. That is quantum computing in 2026, in one tiny experiment you can run yourself for free. ## What you do about the noise Briefly, because it sets up later posts. You fight the noise three ways. You take more shots, so the statistics of the correct answer stand out more clearly above the random wrong ones, since the true signal grows with shots while the random scatter averages out. You apply error mitigation, which for the Estimator primitive means the readout mitigation and other resilience options built into the runtime, and for raw counts from a Sampler means doing what I did above, inverting the published assignment matrix yourself. And most importantly, you keep your circuits as shallow as you possibly can, because every gate and every microsecond is a chance to decohere. Shallow, few gates, many shots. That is the survival strategy on today's hardware, and it is why the observability habit from the pipeline post, checking your circuit's depth before you run, is not fussiness. It is how you keep the noise from eating your answer. ## A quick test before you move on Close this and answer in your own words. On a perfect simulator, the Bell state gives only 00 and 11\. On real hardware, why do 01 and 10 show up at all? If you cannot name at least gate error, readout error, or decoherence, reread the section on where the wrong answers come from. Given a backend's published readout error for the two qubits you used, how would you predict how many shots should land in the wrong bins? If you cannot get to "one qubit misread means the two disagree", revisit the accounting section. Why do these four numbers not prove that the state was entangled? If phase and a second measurement basis are not in your answer, reread the caveat, because that is the claim most first hardware write ups get wrong. And why did switching on error suppression not help my circuit? If your answer does not connect each technique to the specific failure mode it targets, reread the null result. ## Where I am learning it Free. IBM Quantum gives real hardware access through the IBM Quantum Platform and the Qiskit Runtime, and [their documentation](https://quantum.cloud.ibm.com/docs/en/guides) walks through connecting, picking a backend, and submitting a job better than any third party guide. The runtime docs on primitives are the current, versioned source for the execution part. But the piece most tutorials never point at is the backend properties: the readout errors, the assignment matrix, the gate errors, the coherence times, all published, all free, all sitting one attribute away from the object you already have in your hands. Every published readout and gate figure in this post came from there. The single best teacher was queueing a trivial circuit onto a real chip and then sitting down with the device's own numbers to work out where the fifty impossible outcomes came from. ## What it actually felt like I want to be honest about the non technical part, because this was a milestone and milestones are worth marking. For most of this series, quantum had been math on a page and numbers from a simulator. Real, but abstract, the way anything is abstract until it touches the physical world. Submitting that job and waiting in the queue, knowing that somewhere a chip held at a temperature colder than deep space was about to run the exact two gates I wrote, and then watching the results come back, was still the moment it stopped being a topic and became a thing I had actually done, however many jobs I had already put through these machines from inside my own tooling. The noise was a part of that, not a disappointment in it. A clean fifty fifty would have looked just like the simulator and taught me nothing new. Those fifty impossible outcomes were the machine telling me, in its own language, that it was real, physical, imperfect, and mine to wrestle with. And the part that surprised me was how far you can get by taking that seriously. Reading that quantum hardware is noisy is information. Working out from the device's own published numbers that four fifths of your damage happened in the final measurement, then removing seventy percent of it in post processing, then watching a suppression technique do nothing because your circuit did not have the problem it solves, then realising your own histogram does not prove the thing you were about to claim it proved, is a different kind of understanding entirely. If you are learning this too, get onto real hardware as early as you can, even for the most trivial circuit, and then do not stop at the histogram. ## The gap is the whole game Until this run, quantum lived on my simulator, perfect and clean. This run put it on a real machine, and the answer came back with fifty outcomes that should not exist. That gap, between the flawless math and the noisy chip, is not a disappointment. It is the entire engineering challenge of quantum computing, made visible in four numbers. The simulator shows you what the math promises. The hardware shows you what we can actually build, today, in 2026\. Closing the distance between those two is what thousands of brilliant people are spending this decade on. And now you have seen the distance yourself, on the simplest circuit there is, which means you understand the problem better than anyone who has only read that quantum computers are noisy. You watched the noise arrive, and then you counted it. ### Running a quantum job like an automation pipeline URL: https://tuguidragos.com/running-a-quantum-job-like-an-automation-pipeline/ Last updated: 2026-10-03T13:35:12.000Z My day job, for years, has been wiring up automations. Pipelines. Something comes in, it moves through a sequence of stages, each stage transforms it, the flaky external bits get handled, and structured output comes out the other end. I have built a lot of these, and the shape is burned into how I think. The first time I ran a real quantum job, end to end, I had a small jolt of recognition. It is the same shape. Define the work, adapt it to the machine that will run it, execute it while handling the parts that are unreliable, collect and parse the output. That reframe did more to make the Qiskit runtime click for me than any amount of physics. So here is a quantum job as what it actually is to a builder: a pipeline. ## Every pipeline has the same four stages Strip any automation down and you find the same skeleton. You define the work, declaratively, as a description of what should happen. You adapt that description to whatever system is going to run it, because the abstract plan is never quite what the runner wants. You execute, and executing means dealing with retries, rate limits, and things that fail. And you collect the output and parse it into whatever comes next. A quantum job has exactly these four stages, and naming them that way takes most of the mystery out. Build, transpile, run, collect. Let me walk each one in those terms. ## Stage one: build is defining the job The quantum circuit is your job definition. It is declarative, the same way a good pipeline config is declarative: you describe the operations and their order, you do not micromanage the execution. Gates in sequence, then measurement. ```python from qiskit import QuantumCircuit qc = QuantumCircuit(2, 2) qc.h(0) # the work, described qc.cx(0, 1) qc.measure([0, 1], [0, 1]) ``` This is the entangling circuit from [the entanglement post](https://tuguidragos.com/entanglement-explained-without-the-spooky/), but the point here is not what it computes, it is what it is. It is a specification. Nothing has run. You have written down the work, exactly as you would define a workflow before any of it executes. Clean separation between describing and doing, which is the first thing any pipeline gets right. ## Stage two: transpile is adapting to the target Your circuit is abstract. It uses whatever gates were convenient to think in. The machine that runs it, real or simulated, has its own fixed set of operations it can actually perform. So before it can run, the circuit has to be rewritten into the target's native operations. That is transpilation, and if you have ever had a build step that compiles or adapts your job for a specific runner, you already understand it. ```python from qiskit import transpile native = transpile(qc, basis_gates=["rz", "sx", "x", "cx"], optimization_level=1) print(sorted({instr.operation.name for instr in native.data})) # ['cx', 'measure', 'rz', 'sx'] ``` Run it. The Hadamard you wrote is gone, recompiled into the gates this target actually supports, and the transpiler has quietly tried to optimise the circuit while it was at it. This is exactly the compile for the target step in any deployment. One thing to be clear about, because it matters later. That list of four gates is a target I invented for the example. A real device does not let you choose: it publishes its own native gate set, and yours may not contain a CNOT at all. Several current IBM machines implement CZ instead, so a CNOT you write [gets rewritten into a CZ wrapped in single qubit rotations](https://tuguidragos.com/quantum-circuits-ibm-hardware-n8n/) before it ever reaches the chip. When you compile for real hardware you hand the transpiler the backend's own target rather than a hand written list, and it uses that machine's gates, its qubit connectivity, and its error rates to decide what your circuit becomes. Same step, better informed. A local simulator and a real device get compiled differently for the same reason you build differently for staging and production. ## Stage three: run through a primitive is execution Now you execute, and modern Qiskit runs jobs through a primitive, the object from [the version trap post](https://tuguidragos.com/the-version-trap-that-cost-me-an-evening-qiskit-2-x/). There are two. A Sampler when you want the measurement outcomes, the counts. An Estimator when you want a single expectation value. For a circuit with measurements and a distribution to collect, you reach for the Sampler. The execution parameter that will feel most familiar is shots. A quantum result is [a distribution, not a single value](https://tuguidragos.com/measurement-is-not-magic-the-born-rule-by-hand/), so you run the circuit many times and tally the outcomes, and shots is how many times. It is your sample size. More shots, a cleaner estimate of the distribution, at the cost of more runtime. If you have ever tuned a batch size or a sample count against a time budget, this is the same dial. The other familiar idea is environments. Running on a local simulator is your dev environment: fast, free, deterministic in its distribution, always available. Running on real hardware through the cloud runtime is production: there is a queue, you wait your turn, it costs real time, and the results come back noisy because a physical device is an imperfect machine. Treating the simulator as dev and the hardware as prod, and not being surprised when prod is slower and messier, is exactly the right instinct. ## Stage four: collect and parse the output The job finishes and hands back a structured result, and you parse it the way you parse any API response: reach into the known shape and pull out the field you want. For a Sampler, the counts live under the classical register you measured into. ```python from qiskit.primitives import StatevectorSampler result = StatevectorSampler().run([native], shots=1024).result() counts = result[0].data.c.get_counts() print(counts) # {'00': ~512, '11': ~512} ``` Run it. You index the result for your circuit, [reach into its data, name the register](https://tuguidragos.com/the-python-you-actually-need-for-quantum-computing/), and get the counts dictionary back. This is the .c versus .meas gotcha from the version trap post, and it is the same care you take parsing any structured response: know the shape, name the field correctly, do not assume. Out comes a clean dictionary you can feed into whatever happens next. ## The whole pipeline, in one piece Put the four stages together and the job reads like any pipeline you have written. ```python from qiskit import QuantumCircuit, transpile from qiskit.primitives import StatevectorSampler # 1. build: define the work qc = QuantumCircuit(2, 2) qc.h(0); qc.cx(0, 1) qc.measure([0, 1], [0, 1]) # 2. transpile: adapt to the target native = transpile(qc, basis_gates=["rz", "sx", "x", "cx"], optimization_level=1) # 3. run: execute through a primitive result = StatevectorSampler().run([native], shots=1024).result() # 4. collect: parse the structured output print(result[0].data.c.get_counts()) # {'00': ~512, '11': ~512} ``` Run it. Build, transpile, run, collect. The Bell correlation comes out the end as a tidy dictionary, only 00 and 11, just as the entanglement post promised. Nothing about the flow is exotic. It is the pipeline skeleton you already know, with quantum work in the middle. ## When the output is a metric, not rows Sometimes a pipeline does not return a table of records, it returns a single aggregate, a KPI, one number that summarises the run. Quantum jobs have that mode too, and it is the second primitive. The Estimator does not give you counts, it gives you an expectation value, one number summarising a property of the state. ```python from qiskit.primitives import StatevectorEstimator from qiskit.quantum_info import SparsePauliOp bell = QuantumCircuit(2); bell.h(0); bell.cx(0, 1) value = StatevectorEstimator().run([(bell, SparsePauliOp("ZZ"))]).result()[0].data.evs print(float(value)) # 0.9999999999999998, which is 1 up to floating point ``` Run it. You get one, as close to it as floating point allows, which is the perfectly correlated answer for the Bell state: the two qubits always agree, so the ZZ correlation is maximal. That trailing rounding error is worth noticing rather than ignoring, because it is the first hint of something the algorithm posts will keep running into: expectation values arrive as floating point numbers and you compare them with a tolerance, never for exact equality. Notice also that there is no measurement on the circuit and no counts to parse, just a number out. So the rule mirrors ordinary pipelines. Want the full distribution, the rows? Sampler. Want one summary metric? Estimator. Choosing the right primitive is the same call as choosing whether your job emits records or a single computed figure, and getting it right keeps the rest of the flow clean. ## Batching: many circuits, one job Every primitive takes a list, not a single circuit, and that is not incidental. It is batching, the same instinct you use to amortise overhead anywhere: send many units of work in one job instead of paying the round trip cost over and over. ```python a = QuantumCircuit(1, 1); a.h(0); a.measure(0, 0) b = QuantumCircuit(1, 1); b.x(0); b.measure(0, 0) result = StatevectorSampler().run([a, b], shots=1000).result() print(result[0].data.c.get_counts()) # {'0': ~500, '1': ~500} print(result[1].data.c.get_counts()) # {'1': 1000} ``` Run it. Two circuits, one job, and you index the result by position to get each one's counts back, the first a fifty fifty superposition and the second a certain 1\. On real hardware this matters even more than locally, because each submission waits in a queue, so bundling related circuits into one job is the difference between waiting once and waiting ten times. If you have ever batched API calls to dodge rate limits, you already have the instinct. It transfers directly. ## Observability: inspect before you run The last habit any serious pipeline has is looking before you leap. You do not fire a job blindly into production, you inspect what you are about to send, its size, its cost. A transpiled circuit tells you the same things, and you should read them before spending a slot on real hardware. ```python qc = QuantumCircuit(2); qc.h(0); qc.cx(0, 1); qc.measure_all() native = transpile(qc, basis_gates=["rz", "sx", "x", "cx"], optimization_level=1) print("depth:", native.depth(), "ops:", dict(native.count_ops())) # depth: 5 ops: {'rz': 2, 'measure': 2, 'sx': 1, 'cx': 1, 'barrier': 1} ``` Run it. You see the depth, how many sequential layers the circuit is, and the operation counts. The barrier in there is not a gate, it is a marker that measure\_all inserts to stop the transpiler from optimising across the measurement, and seeing things in the output that you did not write yourself is itself part of the lesson: what you built and what actually gets sent are two different objects. On a noisy device these numbers are your cost and risk estimate: more depth means more time for the qubits to decohere, which a later post gets into, and more two qubit gates means more error, because those are the expensive, error prone ones. Reading the transpiled circuit before running it is exactly the observability step of logging a compiled job before you ship it. You find out what you actually built, and roughly what it will cost, while it is still cheap to change your mind. ## Parameterised circuits: a job template with inputs One more pattern that maps cleanly. Often you do not want a single fixed job, you want a template with a variable you fill in at run time, the same circuit run at many settings. Qiskit supports exactly this with parameters: you leave a value symbolic when you build, and bind it just before running. ```python import numpy as np from qiskit.circuit import Parameter theta = Parameter("theta") qc = QuantumCircuit(1, 1) qc.ry(theta, 0) # a rotation by an angle you have not chosen yet qc.measure(0, 0) runs = [qc.assign_parameters({theta: 0.0}), qc.assign_parameters({theta: np.pi})] result = StatevectorSampler().run(runs, shots=1000).result() print(result[0].data.c.get_counts()) # theta=0 -> {'0': 1000} print(result[1].data.c.get_counts()) # theta=pi -> {'1': 1000} ``` Run it. The circuit was built once with the angle left open, then bound to two values and run as one batched job, giving a certain 0 at angle zero and a certain 1 at angle pi. This is the job template with variables that every pipeline person knows: define the shape once, parameterise the part that varies, sweep the values. It is the backbone of [the variational algorithms](https://tuguidragos.com/three-quantum-ways-to-diagonalize-a-giant-matrix/), where an optimiser tunes the parameters between runs to search for an answer, but the pattern itself is the plain one you already use whenever you run the same workflow across a range of inputs. ## The instincts that transfer, and the one that does not Most of what I know about running pipelines carried straight over. Batching: you hand the primitive a list of circuits because you can run many in one job, the same way you batch work to amortise overhead. Environments: simulator is dev, hardware is prod, and you debug on the cheap one before you spend on the slow one. Observability: you inspect the transpiled circuit before running it, the way you would log the compiled job before shipping it, so you can see what actually got sent. Unreliable dependencies: hardware noise is the flaky external service you design around, with more shots and error mitigation, the statistical corrections applied after the run, instead of naive retries. But I want to be honest about where the analogy breaks, because oversold analogies do damage. In an ordinary pipeline you can inspect any intermediate value, log it, print it, set a breakpoint and look. You cannot do that inside a quantum circuit. Measuring an intermediate state collapses it and destroys the computation, so there is no print debugging in the middle, no peeking at the qubits mid flight. And the output is fundamentally a distribution, not a single deterministic value, so you think in shots and statistics rather than exact returns. The skeleton is the same. The thing flowing through it obeys different rules, and pretending otherwise will mislead you. Know the shape that transfers and the substance that does not. ## A quick test before you move on Close this and answer in your own words. What are the four stages of a quantum job, and what does each one correspond to in an ordinary pipeline? If you cannot get build, transpile, run, collect and tie each to something familiar, reread the four stages. What does the shots parameter control, and what is the everyday analogy? If sample size is not in your answer, go back to the execution stage. Why do you hand a real backend's target to the transpiler instead of a list of gates you chose yourself? If the machine's own gates, connectivity and error rates are not in your answer, reread the transpile stage. And where does the pipeline analogy break down? If you cannot name that you cannot inspect intermediate state and that the output is a distribution, reread the section on the instincts that transfer, because that is the part that keeps the analogy from misleading you. ## Where I am learning it Free, and this is the part where my existing world and the new one overlap most, so it went quickly. The Qiskit documentation on primitives and on the runtime is the current, versioned source for how jobs actually run, and unlike a lot of older material it is built around exactly this build, transpile, run shape. The local primitives, the ones in the examples here, run on your own machine for free, which makes them the perfect dev environment to [practise the whole flow](https://tuguidragos.com/i-built-a-place-to-practise-qiskit/) in before you ever touch a real device or its queue. As always, I learned it by running it, because a pipeline I have only read about is not one I trust until I have watched my own output come out the end of it. Every output comment in this post is what my machine actually printed, floating point noise included, apart from the ones marked with a tilde, which are typical values because sampling varies from run to run. ## The runtime stopped being foreign The cloud runtime, the primitives, the transpilation step, all of it looked like a new and intimidating system when I started, a pile of quantum specific machinery I would have to learn from scratch. It is a pipeline. Define the work, adapt it to the target, execute it while handling the unreliable parts, collect and parse the output. I have built that shape a hundred times in a completely different domain, and the shape did not change just because the work in the middle turned quantum. That is the quiet advantage of coming to this as a builder. The physics is genuinely new and you have to earn it. But a surprising amount of the surrounding engineering is not new at all, it just wears unfamiliar names. Recognising the pipeline under the runtime is worth more than it sounds, because it means your instincts are more transferable than the vocabulary lets on. Mine were. Yours probably are too. ### I made 58 themes for myself, then measured every colour in them URL: https://tuguidragos.com/58-vs-code-themes-measured/ Last updated: 2026-10-07T03:09:27.000Z ## I change the colours when the work changes Some weeks I want the editor to disappear. Some weeks I want it to be the most interesting thing on the screen. I have never managed to keep one theme for longer than a couple of months, and at some point I stopped treating that as a lack of discipline. It is just how I work. A hard debugging week and a quiet refactoring week do not want the same room. So I made one. Then I made another one, because the first was wrong for a Tuesday. Then I noticed I was making a set, and that the set had a problem: the ones I made in the afternoon were comfortable to read, and the ones I made at one in the morning were not, and I could not tell which was which by looking at them. Enthusiasm is not a measurement. There are 28 families now, each in a dark and a light variant, plus a high contrast pair, which comes to 58\. I built every one of them for myself, which is not the first time [I have made the thing I could not find.](https://tuguidragos.com/i-built-a-place-to-practise-qiskit/) I put them on the marketplaces because there was no reason not to, and because if you also change your editor when the work changes, you now have somewhere to change it to. --- ## I read the code, which is why the colours matter I found VS Code years ago and never left. That is not a verdict on the alternatives, it is the ordinary loyalty you develop for something simple that shows you the code and then gets out of the way. I work in it every day and I have never wanted to move. A preference is not a testing strategy, though. The themes ship to Open VSX as well as to the Marketplace, which is where VSCodium, Cursor, Windsurf, Gitpod and code-server install from, so I installed them in Cursor and in Windsurf and went through the families there myself. They come out identical: the same 991 values, the same surfaces, the same structure, because the forks inherit the same theming layer underneath. I could not have told you that without opening them, and telling you anyway is exactly the habit this project exists to avoid. What I do have an opinion about is reading. I spend my days building systems where one wrong assumption quietly breaks everything downstream, and the only defence I have found is to read the thing, all of it, including the parts I am sure about. It is the same reflex that had me [submit one quantum circuit 20 times](https://tuguidragos.com/ibm-quantum-error-code-1506-1603/) to find out which error the machine would actually return. I want to know what is happening in the heart of my own projects. That is the whole reason a colour theme is worth this much trouble. If you are going to read a file line by line, legibility is not decoration, it is the floor you are standing on. A comment that has been dimmed slightly too far costs you nothing on the day you skim and everything on the day you are hunting. So the rule I gave myself was simple: I choose the colours, and then a machine checks whether I can actually see them. --- ## Every family comes from something real The other rule was that no palette gets invented. If a family needs six colours, those six come from something real, and I go and find out what it is before I open the editor. This started as a constraint to stop me fiddling and turned out to be the most useful decision in the project. A palette pulled out of the air can be adjusted forever, because nothing says it is finished. A palette taken from an emission spectrum is finished when the spectrum runs out. Selenium is a good place to start, because it is the one I keep coming back to. Selenium toning is a darkroom process: you bathe a silver print in a selenium bath and the blacks shift toward purple while the tonal range stretches at the same time. The result is an image that is warm in the shadows and cool in the highlights at once. So this family does not pick six separate hues at all. It picks a ladder of lightness and lets the hue rotate as the ladder descends, which is why it stays out of the way for hours. ![VS Code in Tapetum Selenium, a dark aubergine editor showing CSS, source control on the left, chat on the right.](https://tuguidragos.com/content/images/2026/08/tapetum-selenium-dark.png) Selenium toning pushes the blacks of a silver print toward purple. This is the shadow end of the process, warm and low. ![The same file in Tapetum Selenium Light, a warm off white editor with muted purple and slate syntax.](https://tuguidragos.com/content/images/2026/08/tapetum-selenium-light.png) The same bath cools the highlights while it stretches the range. One ladder of lightness, read from the other end. --- ## A laser cannot emit magenta, so Coherence does not contain one A mixed argon and krypton ion laser puts out these six visible lines, among others, from a single tube at once, which is what a white laser actually is. Argon gives 457.9, 488.0 and 514.5 nanometres. Krypton gives 413.1, 568.2 and 647.1\. Six lines, six roles in the code, and the mapping writes itself. Then I got to the part that made me like this family more than any of the others. There is no magenta in it. Not because I ran out of roles, and not because it would have clashed, but because magenta is not a spectral colour. It does not have a wavelength. Your brain manufactures it when the red and blue cones fire together and the green ones stay quiet, and no laser in the world can emit one. A family built out of laser lines cannot contain a colour that lasers cannot make, so it does not. The interface in Coherence falls to true black for the same reason. If the premise is that everything you see is emission, then the surface the emission sits on should not be emitting anything. I did not decide any of that. The physics decided it and I typed it in. --- ## Water eats red first, so Hadal's red is almost grey Depth removes colour in a fixed order. Red is gone by about 5 metres, orange by 20, yellow by 50, green by 100, and below 200 metres there is only blue left. Hadal makes that the rule instead of the theme: the colours that survive deepest get the most light and the most saturation, and the ones that die shallow are left faded on purpose. Which means the red in Hadal is almost grey, and that is not a mistake I failed to catch. It is the point. If you are down there, red is the first thing you lose. ![VS Code in Tapetum Hadal, a deep sea blue editor where cyan and green carry the code and the red sits back.](https://tuguidragos.com/content/images/2026/08/tapetum-hadal-dark.png) Below 200 metres there is only blue left, so the colours that survive deepest get the most light. The red is almost grey, and it should be. ![Tapetum Hadal Light, the same file on a pale blue white ground with teal and green syntax.](https://tuguidragos.com/content/images/2026/08/tapetum-hadal-light.png) The light half is not the dark one inverted. Every light palette here was placed on its own ground, because a colour that reads on black usually fails on white. --- ## One material, two colours, so one family with two variants Aerogel is 99% air and it scatters short wavelengths the same way the sky does. Hold a block against a dark background and it looks blue. Hold it up to a lamp and the light that comes through is amber. Same material, no pigment, two completely different colours depending on which side of it you are standing. Every other family here has a dark variant and a light variant because I made two. Aerogel has them because the material does. The dark one is the reflection and the light one is the transmission, and it is the only pair in the collection that comes from one physical fact rather than two. ![VS Code in Tapetum Aerogel, a cold blue black editor, the colour aerogel shows against a dark background.](https://tuguidragos.com/content/images/2026/08/tapetum-aerogel-dark.png) Aerogel is 99% air and scatters short wavelengths the way the sky does. Against a dark background, a block of it is blue. ![Tapetum Aerogel Light, a warm amber white editor, the colour aerogel shows when light passes through it.](https://tuguidragos.com/content/images/2026/08/tapetum-aerogel-light.png) Hold the same block up to a lamp and what comes through is amber. One material, no pigment, two colours, and therefore two variants. --- ## A dichroic filter splits, so there is no third hue A dichroic filter absorbs nothing. It splits. What passes through has one colour and what reflects has exactly its complement, and there is no third option anywhere in the system. So Dichroic has two hues in the whole of the code. Structure travels through the filter and takes the teal. Data reflects back and takes the magenta. That is the entire palette, and after ten minutes with it you stop reading syntax categories and start seeing the shape of the file. The terminal is the one place that breaks the rule, because red and green have to stay red and green. A theme that renames the colour of a failing test to fit its own concept has stopped being a theme and started being a nuisance. ![VS Code in Tapetum Dichroic, a dark green black editor with only two hues, teal for structure and magenta for data.](https://tuguidragos.com/content/images/2026/08/tapetum-dichroic-dark.png) A dichroic filter absorbs nothing, it splits. Structure passes through and takes the teal, data reflects back and takes the magenta. ![Tapetum Dichroic Light, the same two hue split, deep teal and magenta on a pale pink ground.](https://tuguidragos.com/content/images/2026/08/tapetum-dichroic-light.png) On a pale ground both hues have to go deeper to hold their contrast, but they stay exact complements. There is still no third colour in the code. --- ## Cochineal is a colour you had to farm insects for Cochineal is a scale insect that lives on cactus, and carminic acid is what it makes to keep ants away. It gave Europe a scarlet stronger than any it had. It was worth close to silver by weight, it took roughly 70000 insects to make a pound of dye, and for two centuries Spain guarded the source so carefully that most of Europe did not know it was an animal at all. In this family the background is the crimson sunk into the cloth and the syntax is the same dye at different strengths. It is the warmest thing in the collection, and it is the one that gets the strongest reactions, in both directions. ![VS Code in Tapetum Cochineal, a crimson black editor with scarlet and rose syntax.](https://tuguidragos.com/content/images/2026/08/tapetum-cochineal-dark.png) Carminic acid gave Europe a scarlet stronger than any it had, worth close to silver by weight. Here the background is the dye sunk deep into the cloth. ![Tapetum Cochineal Light, the same file on a blush white ground with deep carmine syntax.](https://tuguidragos.com/content/images/2026/08/tapetum-cochineal-light.png) The light variant is the same dye at a fraction of the strength, the way a cochineal wash looks on undyed linen. --- ## Some of them colour by what the code does, not what it is Almost every theme in existence answers one question: what kind of symbol is this. Keyword, string, number, type. It is a good question and 22 of the families answer it, but it is not the only one worth asking, and the answer is a property of the theme rather than a property of the editor, which means changing theme can change the question. Effect colours by whether a line touches the outside world. `await`, `async`, `throw`, `catch` and every mutation burn. Functions that only compute, the types, and the data stay cold. It is the same idea a type system uses to track effects, except you can see it without reading a signature, and the first time you open a file you have not touched in a month it tells you where the danger lives before you have read a word. ![VS Code in Tapetum Effect, a near black green editor where mutation and async burn orange while pure code stays cold.](https://tuguidragos.com/content/images/2026/08/tapetum-effect-dark.png) Effect colours by what a line touches rather than what it is. await, async, throw and every mutation burn; pure computation stays cold. ![Tapetum Effect Light, the same split on a pale green white ground, hot colours for anything that touches the world.](https://tuguidragos.com/content/images/2026/08/tapetum-effect-light.png) The hot colours were picked again from scratch for white, not darkened. Burning has to still read as burning when the room is bright. There are five more like it. Provenance colours by where a symbol came from: gold for what you wrote here, cyan for what the language handed you, violet for what cannot change, and the keywords are deliberately dimmed, because you already know `function` is a function. Borrow colours by whether a value can change, and lights up a name at the exact point you write to it. Palimpsest pushes keywords, imports, punctuation and type annotations back toward the background, so only the names you chose stay lit. Silverpoint and Stratum remove hue from the code entirely and do the work with tone, weight and italic. Three of those read your language server rather than the grammar, using the semantic tokens it emits, including the `declaration`, `defaultLibrary`, `readonly`, `static`, `modification` and `async` modifiers that most themes never touch. These are six of the twenty eight families. The rest are in the repository, each with the thing it came from written next to it: Fraunhofer from the absorption lines Joseph von Fraunhofer found in sunlight in 1814, Epitaxy from the real emission peaks of materials grown on a silicon wafer, Persistence from oscilloscope phosphor decay, Incandescence from the temperature scale a blacksmith reads by eye, Noctiluca from living light, Cherenkov from the blue glow in a reactor pool. --- ## Placing the colours was the easy half Colour is a judgement call, so every value in here was placed by hand. What a machine is good for is catching what a person misses, and it turns out a person misses an enormous amount. The editor registers 966 colour keys. Not the twenty you think of, not the two hundred a normal theme sets, but 966, covering the chat surfaces, the agent panels, inline edit, notebook cells, the modern tabs, the sticky scroll, the ruler beside the minimap. Every one of the 58 themes sets all of them, plus the terminal palette and a handful of compatibility keys, which comes to 991 values per theme. That number is not typed anywhere. A tool reads it out of the running editor, because a hand written list would go stale the first time Microsoft shipped a new surface. The same goes for what gets checked: another tool reads the workbench stylesheet out of the installed editor and collects every foreground and background used in the same rule, so what gets measured is what the editor actually paints together, not what I assumed it paints together. That currently comes to 83 pairs. A third tool reads the binary for the keys whose default is another key, 74 of them, which catches the class of bug where a value quietly contradicts the surface underneath it. On top of that: every syntax colour against its own background, every pair of coloured roles against each other in CIEDE2000, the standard colour difference formula, so no two roles collapse into one, and every pair of families against each other so no two families are the same theme wearing a different name. The closest two families in the collection still average 12 dE apart, dE being that formula's unit of difference. Syntax clears a contrast ratio of 4.5 to 1 against the background. Comments clear 4.0, because comments are supposed to recede. The lowest value anywhere in 58 themes is 4.07, and I know exactly which comment in which theme it is. I also implemented APCA next to WCAG, because WCAG is a 2008 formula built on 1990s colour standards that does not model dark backgrounds well, and on a dark theme it will happily tell you that a colour is fine when your eye disagrees. The implementation is checked against the canonical reference values on every run, so if I break the measuring stick, the build tells me instead of quietly measuring everything wrong. For comparison, since it is the fairest one available: the themes VS Code ships set between 36 and 347 of those keys, and several of them fail their own contrast pairs. That is not a criticism of the people who made them. It is what happens to any theme that is maintained by hand against a surface that grows every month. --- ## An optimiser chose them once and made them worse At one point I did the thing every engineer does and tried to automate the taste. I wrote an optimiser, gave it the contrast floors and the separation targets as an objective, and let it choose the colours. It converged on something worse. Two of the families came out under 2 dE apart, which is close enough that most people would not see a difference at all, and it had bleached the keywords toward grey because grey is an easy way to satisfy a contrast constraint without satisfying anything else. Every number was green and the result was dead. I threw it away and went back to placing colours by hand. The tooling kept the part it is good at, which is telling me when I am wrong, and lost the part it was never good at, which is deciding what I want. --- ## The parts I got wrong Two examples from the last week, both found by looking rather than measuring. The first was a grey pillar. In the diff editor the scrollbar is 30 pixels wide and always visible, and I had made every slider fully opaque some releases earlier, after discovering that the one over the minimap was letting 90% of the minimap show through it. Over a file of eight lines the slider filled the entire track, so a small diff had a solid grey column standing next to it doing nothing. The overcorrection was mine and it took someone pointing at it for me to see it. The sliders are translucent now and firm up under the hand. The second was louder. Every family takes its accent from the same place its syntax comes from, so how saturated that accent is depends entirely on what the family was drawn from: 6 in Silverpoint, 94 in Cherenkov. In a cursor or a focus ring that spread is the whole point of the collection. Filled into a button it is something else, and I kept glancing at the extensions panel thinking something needed answering. Nothing did. It was just the accent, sitting in a rectangle. Filled surfaces now cap the saturation at 45 and leave the hue and the lightness exactly as they were, so a family still looks like itself and stops raising its voice. 33 of the 58 were over the line and came down. The other 25 did not move. Both of those are now checks. That is the actual working method here: I notice something with my eyes, and then I turn it into something the build can notice without me. --- ## Where it lives ``` code --install-extension tuguidragos.tapetum ``` Or from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=tuguidragos.tapetum), or from [Open VSX](https://open-vsx.org/extension/tuguidragos/tapetum), which is where VSCodium, Cursor, Windsurf, Gitpod and code-server install from. Press `Cmd K` then `Cmd T`, or `Ctrl K` then `Ctrl T`, and type a family name to filter. Every family is `Tapetum ` for the dark variant and `Tapetum Light` for the light one. The [code is on GitHub](https://github.com/TuguiDragos/tapetum), all 58 palettes and the tools that check them. If you want to see whether any of this is true rather than take my word for it, clone it and run `node tools/analyze.mjs`. If you would rather choose before you install, [tapetum.tuguidragos.com](https://tapetum.tuguidragos.com) previews all 58 in the browser, with screenshots from VS Code. --- ## If something looks wrong, open an issue and we will measure it Every colour in here was placed by hand, which means some of them are wrong in ways I cannot see from my own screen. Your languages are not my languages, your monitor is not my monitor, and there are 58 of these. So if something reads badly, or a family looks off next to the code you spend your day in, [open an issue](https://github.com/TuguiDragos/tapetum/issues) and we will measure it together. That is how the merge conflict colours got fixed, and the grey pillar in the diff editor, and the buttons that looked like notifications. Every one of those came from someone looking at the thing and saying what hurt, which turned out to be a better test than any tool I had written. ### Relative phase: the invisible engine of quantum URL: https://tuguidragos.com/relative-phase-the-invisible-engine-of-quantum/ Last updated: 2026-10-03T13:35:11.000Z For a while, quantum gates felt arbitrary to me. The Z gate flips the sign of |1⟩, and my honest reaction was, so what. The probabilities do not change, you cannot see a sign in a measurement, so why does anyone care about a gate that just decorates the state with a minus. I could apply these gates and get the right matrices out, but they felt like sign juggling with no point. The thing that fixed it, completely, was one distinction: global phase versus relative phase. There is a phase the probabilities genuinely never see, and you are right to ignore it. And there is a phase that the next gate lives on, which is where all the action is. Telling them apart is the moment gates stop feeling arbitrary and start feeling like writing. I have been promising this post for a few entries now. Here it is. ## Quick recap: phase is an angle From the [complex numbers post](https://tuguidragos.com/complex-numbers-are-the-gateway-to-quantum/): every amplitude has a magnitude and a phase, a length and an angle. Square the magnitude and you get the probability. The phase, the angle, does not show up in a single measurement at all. That was the puzzle I left open. If the phase is invisible to measurement, when does it ever matter? The answer is that it depends entirely on which kind of phase you mean. ## Global phase: real, and completely meaningless Take any quantum state and multiply the whole thing by a phase, by e to the i theta for some angle. Every amplitude turns by the same angle. The claim is that this changes nothing you could ever observe, for any measurement at all. ```python import numpy as np psi = np.array([1, 0]) # |0> rotated = np.exp(1j*0.7) * psi # the whole state turned by 0.7 radians print(np.allclose(np.abs(rotated)**2, np.abs(psi)**2)) # True Q, _ = np.linalg.qr(np.random.randn(2,2) + 1j*np.random.randn(2,2)) # some other basis print(np.allclose(np.abs(Q.conj().T @ rotated)**2, np.abs(Q.conj().T @ psi)**2)) # True, in any basis ``` Run it. The probabilities are identical, because turning every amplitude by the same angle leaves every length unchanged, and probabilities are about lengths. And not only in this basis. The conjugate you take when you square an amplitude cancels the phase exactly, and that cancellation does not care which basis you are squaring in, which is what the second check confirms. This is a global phase, an overall turn applied to the entire state, and it is physically undetectable. Two states that differ only by a global phase are the same state. You can multiply by whatever overall phase you like and it makes no difference to anything. This is also the small print from [the gates post](https://tuguidragos.com/quantum-gates-are-just-matrices-you-can-run-by-hand/), where a rotation came out equal to the X gate up to an overall phase factor. That factor was a global phase, and now you know why it was safe to wave away. Global phase is a label with no physical content. Ignore it freely, with one piece of small print that follows. A phase only counts as global when it multiplies the entire state. Put the same phase on one part of a larger state, or on one branch of a controlled operation, and it stops being global and becomes relative, which is the opposite of meaningless. That is a mechanism rather than a caveat, and it gets a post of its own. ## Relative phase: invisible to one measurement, and everything Now the other kind. Instead of turning the whole state, change the phase between its parts. Compare |0⟩ + |1⟩ with |0⟩ minus |1⟩, each over √2\. These are the states |+⟩ and |−⟩. They have the same magnitudes on |0⟩ and |1⟩, the same lengths, differing only in the sign, the relative phase, between the two components. Measure either one in the standard basis and you get the same thing: ```python plus = np.array([1, 1]) / np.sqrt(2) minus = np.array([1, -1]) / np.sqrt(2) print(np.round(np.abs(plus)**2, 3)) # [0.5 0.5] print(np.round(np.abs(minus)**2, 3)) # [0.5 0.5] ``` Run it. Both are fifty fifty. In a standard measurement, the relative phase is just as invisible as the global one was. This is exactly why I thought the sign did not matter. If you only ever measure in the standard basis, |+⟩ and |−⟩ look identical, and the minus sign looks like pointless decoration. But they are not the same state, and you can prove it by measuring differently. ## How to make the invisible phase visible The trick is to apply a gate before you measure, to change the question you are asking. Apply a Hadamard to each. ```python H = np.array([[1, 1], [1, -1]]) / np.sqrt(2) print(np.round(H @ plus, 3)) # [1 0] -> |0>, with certainty print(np.round(H @ minus, 3)) # [0 1] -> |1>, with certainty ``` Run it. After a Hadamard, |+⟩ becomes |0⟩ and |−⟩ becomes |1⟩. Now they are not fifty fifty and they are not the same. One gives 0 with certainty, the other gives 1 with certainty. The relative phase that was completely invisible a moment ago has become a definite, perfectly distinguishable outcome. The phase was always there, carrying real information. It just took a gate to turn it into something a measurement could read. That is the entire point I had been missing. A relative phase is invisible to one particular measurement and fully real to the right next operation. ## So that is what the Z gate was doing Go back to the gate that started my complaint. The Z gate flips the sign of |1⟩. Watch what that does to |+⟩. ```python Z = np.array([[1, 0], [0, -1]]) print(np.allclose(Z @ plus, minus)) # True ``` Run it. Z turns |+⟩ into |−⟩. It did not do nothing. It moved the state from one place to a genuinely different place, a place that the standard measurement could not tell apart from the original but that the very next Hadamard could separate completely. The Z gate writes into the relative phase. It looked pointless only because I was looking at it through a measurement that is blind to exactly the thing it changes. Through the right next gate, Z is decisive. The sign was never decoration. It was data, written where the probabilities could not see it. ## S and T write smaller phases Z is the biggest phase move there is, a half turn that flips the sign, and it is the biggest because phase wraps: anything past a half turn is just a smaller turn the other way. The S and T gates from the gates post write smaller, finer phases, a quarter turn and an eighth turn, exactly the complex number rotations from earlier. Watch them act on |+⟩. ```python S = np.array([[1, 0], [0, 1j]]) T = np.array([[1, 0], [0, np.exp(1j*np.pi/4)]]) print(np.round(S @ plus, 3)) # [0.707+0j 0+0.707j] print(np.round(T @ plus, 3)) # [0.707+0j 0.5+0.5j] ``` Run it. S leaves the |0⟩ part alone and turns the |1⟩ part by ninety degrees, putting an i where there was a one. T does the same with a forty five degree turn. Both leave the magnitudes untouched, so both are still fifty fifty in the standard basis, and both have parked the state at a different spot in its relative phase, a spot the next gate can act on. Z, S, and T are a family of phase writers at different resolutions: half turn, quarter turn, eighth turn. None of them changes a probability on its own. All of them change what the next operation will find. One naming aside, because it is this post's own distinction in miniature. T is traditionally called the pi over eight gate, even though the phase it writes is pi over four. The reason is that T can also be written as a sixteenth turn of overall phase times a rotation that splits the phase symmetrically, plus and minus pi over eight, across the two amplitudes. The two spellings differ by exactly a global phase, so they are the same gate, and the name comes from the spelling where the global phase has been pulled out front. A confusing name that dissolves the moment you know which phase is which. ## Chaining it: the phase decides the answer Here is the whole idea in one short chain, the moment the abstract becomes concrete. Start from |+⟩. Take two paths. On the first, go straight to a Hadamard. On the second, apply a Z first, writing a relative phase, then the Hadamard. ```python print(np.round(H @ plus, 3)) # path 1: |+> -> H -> [1 0] = |0> print(np.round(H @ (Z @ plus), 3)) # path 2: |+> -> Z -> H -> [0 1] = |1> ``` Run it. Same starting state, same final gate, and yet one path ends at |0⟩ with certainty and the other at |1⟩ with certainty. The only difference between them was a relative phase, written by the Z, that was completely invisible the instant it was written. The closing Hadamard turned that invisible phase into a definite, opposite answer. This is interference in its smallest form: a phase you cannot measure, made to control an outcome you can. Stare at this until it lands, because every quantum algorithm is this chain scaled up. ## What "measuring in a different basis" really means I have been saying measure in the standard basis and measure differently as if they were obviously distinct things, so let me make that concrete, because it is the mechanism underneath the whole post. The standard basis, the Z basis, asks one specific question: is this qubit |0⟩ or |1⟩. That is the only question a raw measurement knows how to ask. Relative phase is invisible to it because the question it asks does not depend on phase. To ask a different question, you rotate the state with a gate first, and then ask the same old question. Applying a Hadamard before measuring is, in effect, [measuring in the X basis](https://tuguidragos.com/measurement-is-not-magic-the-born-rule-by-hand/), asking is this |+⟩ or |−⟩ instead of is this |0⟩ or |1⟩. And the X basis question does depend on the relative phase, which is exactly why the Hadamard could tell |+⟩ and |−⟩ apart when the bare measurement could not. So there is no special phase detecting hardware. There is one kind of measurement, blind to phase, and you change what it sees by rotating the state into its line of sight first. A relative phase is information that is invisible in one basis and fully visible in another, and the gate you apply before measuring is how you choose the basis. That reframing is what made the rest click for me: the phase is never hidden in some absolute sense, it is just hidden from the particular question you happen to be asking, and you get to change the question. ## Why this is the engine of the whole field Here is the payoff, and it is the reason this distinction is worth a whole post. Quantum algorithms work, at heart, by writing answers into relative phases and then using a later operation to turn those phases into visible outcomes. You set up a superposition, you use gates to stamp a pattern of relative phases across it, phases that no measurement could yet read, and then you arrange for those phases to interfere, reinforcing the answer you want and cancelling the ones you do not, until a final measurement reads out something that was hidden in the phases all along. The computation lives in the relative phase, invisible, until the last step cashes it out into probability. That is the mechanism behind the speedups, and it is why phase is not a footnote but the main character. Interference, the thing that makes quantum [more than a probabilistic coin](https://tuguidragos.com/a-qubit-is-not-a-coin-under-a-cup/), is just relative phases meeting and adding or cancelling. One piece of honesty before you take that too far. Interference is necessary, not sufficient. There is a whole family of circuits stuffed with relative phase and real interference that an ordinary laptop can simulate efficiently, and, awkwardly, it is the family you build from the H, S and Z above together with CNOT. What takes you out of it is T, the finest phase writer of the three. That is why the universal set from the gates post was H, T and CNOT rather than H, S and CNOT, a detail I had filed as arbitrary at the time. Interference is the engine. T is part of what makes the engine worth having. You cannot understand a single quantum algorithm without this, and once you have it, the algorithms stop being magic and start being phase choreography. The algorithm posts ahead lean entirely on this idea. To make that less abstract, here is the shape of the simplest real example, the one the algorithm posts will build out properly. Suppose you have a function and you want to know a global property of it, something that would normally take several evaluations to pin down. The quantum trick is to run the function in a way that, instead of returning its answer into a probability, stamps that answer into a relative phase across a superposition. Now the property you care about is encoded in a pattern of phases that no measurement can yet see. Then you apply a Hadamard, or a similar interference step, and those phases reinforce and cancel in just the right way to drive the final measurement straight to the answer, often in a single query where a classical approach would need many. You do not get this for free, and it does not work for every problem, but every case where it does work runs on this same engine: hide the answer in relative phase, then interfere to make it visible. That is the whole game, and you have already seen it in miniature two sections up. ## The mental snap Here is the reframe that did it for me, in two lines. Global phase is the orientation of the whole state, and it means nothing, so throw it away. Relative phase is the internal structure between the parts, and it is everything, because the next gate reads it and interference runs on it. Once I separated those two, every sign flipping gate stopped being arbitrary. A gate that adds a sign is not decorating the state. It is writing into the relative phase, which the next operation will read and act on. Gates went from noise to language the moment I understood which phase actually carries the message. ## A quick test before you move on Close this and answer in your own words. If you multiply an entire state by e to the i theta, what changes about any measurement of it? If your answer is not nothing, reread the global phase section. |+⟩ and |−⟩ give the same result in the standard basis. How would you tell them apart? If you cannot say something like apply a Hadamard first, the section on making the phase visible did not land. And what is the Z gate actually doing to a state, in terms of phase? If your answer is still just flips a sign with no consequence, go back to the section on Z, because that was the whole misunderstanding this post exists to fix. ## Where I am learning it Free. The Bloch sphere helps here, because it draws relative phase and refuses to draw global phase at all, which makes the difference visual instead of abstract. That is worth sitting with, because it is not a gap in the picture, it is the point of it. A qubit state is two complex numbers, four real numbers in total. Normalisation, the rule that the squared lengths add to one, eats one of them. Global phase eats another. The two that survive are exactly the two angles of a point on a sphere, which is why a sphere is a complete picture of a single qubit: it is what is left after you throw global phase away. Relative phase is one of the two coordinates that did survive, and turning it walks the point around its own line of latitude, which for |+⟩ is the equator itself, carrying it from |+⟩ toward |−⟩, a real, visible journey to a distinguishable place. The states that look identical to a standard measurement are the ones at the same height on the sphere, and the relative phase is how far around they have travelled. 3Blue1Brown and the usual explainers cover the geometry. But the thing that actually flipped it for me was the few lines above: take |+⟩ and |−⟩, confirm they measure the same, apply a Hadamard, watch them split. As ever, the distinction I had read a dozen times did not become real until I made the invisible phase turn into a definite outcome on my own machine. ## The invisible thing that runs the machine I spent real time thinking quantum gates were arbitrary because half of what they do is invisible to a measurement. The fix was learning that invisible to a measurement and physically meaningless are not the same thing. Global phase is both, and you can ignore it. Relative phase is invisible to the wrong measurement and decisive to the right next gate, and it is where the entire computation quietly happens. Once the sign flips stopped looking like decoration and started looking like writing into a channel the probabilities cannot read, the gates clicked, and they have stayed clicked. The most important part of the state is the part you cannot directly see. That is not a paradox. It is the machine. ### One circuit, two IBM Quantum error codes: 1506 and 1603 URL: https://tuguidragos.com/ibm-quantum-error-code-1506-1603/ Last updated: 2026-09-13T18:42:30.000Z I submitted the same rejected program to IBM Quantum 20 times in a row. Same device, same 64 shots, same tag, one stored workflow fired 20 times so the payload could not drift. 20 submissions in 19 seconds. 11 came back as reason code 1506\. 9 came back as 1603\. Interleaved, not in 2 blocks. ``` 1603 1603 1506 1603 1506 1603 1506 1506 1506 1603 1603 1603 1506 1506 1506 1506 1603 1506 1506 1603 ``` 2 and a half hours earlier that same evening I had seen the same program return 1506 once and 1603 once, 5 minutes apart, and I nearly published that as "IBM is not deterministic". It would have been true and useless. The 20 runs say something much more specific, and they say it with no exceptions. This is release 0.5.0 of [n8n-nodes-ibm-quantum](https://www.npmjs.com/package/n8n-nodes-ibm-quantum), my community node for running quantum circuits on IBM hardware from inside an n8n workflow: 69 jobs on real hardware during the verification campaign, 98 including the replication evening below, 574 of the Open plan's 600 QPU seconds, 26 problems found. The one worth writing about is the one where the wrong answer was mine, twice. ## The control I ran could only agree with me 3 weeks ago I put `rzz` on real hardware, watched IBM fail it, and published the result. The FAQ on [an earlier post](https://tuguidragos.com/six-defects-verified-n8n-node-ibm-quantum/) answers "Can I use rzz on IBM hardware through OpenQASM 3?" with "Not through the REST API as of August 2026." That answer is wrong, and it was wrong the day I published it. If you read it and routed around a gate you could have used, that one is on me. The mechanism is more useful than the apology. `rzz` is a 2 qubit rotation and it sits in the basis gate set the 3 Heron devices I can reach all report: `cz, id, rx, rz, rzz, sx, x`. When a submission using it failed, I ran what I thought was a control. I removed `rzz`, submitted the rest, and watched it complete. That proved the failure belonged to `rzz` rather than to the surrounding circuit. It could not prove anything else. A control that removes the suspect can only tell you the suspect is involved. It cannot separate *`rzz` is rejected* from *`rzz` written this particular way is rejected*, and the distance between those 2 sentences is this entire release. What makes it worse is that I had already done it properly, in the same campaign, on a different gate. The `id` finding in that post came from 3 control jobs varying one thing at a time, and I wrote a line about it I now get to eat: a theory that sounds right is the most expensive kind of wrong, so I put it on hardware. I did put it on hardware. I put the wrong half of it on hardware. ## What rzz does when you define it The variable I never varied was the definition block. `stdgates.inc`, the OpenQASM 3 standard library, defines no `rzz`. My circuit builder writes bare gate calls with no definitions, so what it emitted was a call to something the loader had never heard of. Qiskit's own exporter writes the definition alongside the call, here with readable parameter names in place of the exporter's generated ones: ```qasm gate rzz(p0) a, b { cx a, b; rz(p0) b; cx a, b; } rzz(0.5) q[0], q[1]; ``` Submitted that way, the job completes. **The gate was never the problem. The missing definition was.** 2 different bugs, 2 different fixes, and I had spent 3 weeks fixing the wrong one, up to and including deleting a working implementation. Then I wrote those same 3 instructions out by hand, without the wrapper, and submitted that: ```qasm cx q[0], q[1]; rz(0.5) q[1]; cx q[0], q[1]; ``` It failed. `Circuit 0: The instruction cx on qubits (0, 1) is not supported by the target system`, reason code 1517\. `cx` is not in the Heron basis, and nothing rewrote it. So the identical 3 instructions run inside a `gate rzz` block and are refused when written flat. **The loader is not inlining the definition and passing the result to the target.** Either it maps the body onto the device basis, or it recognises the gate by name and substitutes the native RZZ. I know exactly which experiment separates those 2, and I have not run it. Submit the same body under a name IBM's basis does not contain, `gate myzz(p0) a, b { cx a, b; rz(p0) b; cx a, b; }`. If it completes, the loader maps bodies. If it returns 1517, it substitutes by name. That is one job and about 2 seconds of quota, I had 26 seconds left when I finished the runs below, and I am writing this before spending them. Naming the control I did not run is the least I can do in a post about the control I ran badly. The timing cannot decide it either. The `rzz` circuit and a bare `cz` baseline both report `circuits_execution_time_ns` of exactly 16080128, identical to the nanosecond, and the 3 defined jobs in the 6 job alternation described below report the same number again. That is either a quantised metric or 2 circuits that really are the same shape at the hardware, and 5 identical integers cannot tell me which. What it does rule out is the naive reading, which is that nothing happens to your circuit between submission and execution. I have been telling people [Qiskit Runtime does not transpile](https://tuguidragos.com/quantum-circuits-ibm-hardware-n8n/). That is true in the way that costs you money, since it will not route your qubits onto a coupling map and it will fail a circuit that needs it. It is not true that the program reaches the hardware untouched. One more thing worth knowing before you reach for the gate. IBM documents [fractional gates](https://quantum.cloud.ibm.com/docs/en/guides/fractional-gates) as `rzz` for angles in the range 0 to π/2 and `rx` for any angle, on Heron only, marked experimental, and incompatible with Pauli twirling, with probabilistic error cancellation, and with zero noise extrapolation by error amplification. I tested one angle, 0.5 radians, well inside that range. And by my own argument above I cannot even say a fractional gate ran, because what completed carried the `cx, rz, cx` body. ## Two codes, two paths, and only one of them tells you why Back to the 20 jobs. [IBM publishes an error registry.](https://quantum.cloud.ibm.com/docs/en/errors) It titles 1506 "QASM parsing failed" and 1603 "Error loading QASM circuit with the standard Qiskit loaders". I had not read it before the campaign, and I have no excuse, because the 1506 message ends with a link to that registry. IBM printed the correction inside the error I was reading. But the registry only names the codes. The job metrics say which one a job is going to get, and the split is total: | | 1603 | 1506 | | ----------------------------- | ---------------- | ---------------- | | jobs | 9 | 11 | | got a running timestamp | **0 of 9** | **11 of 11** | | time from created to finished | 0.401 to 0.891 s | 1.873 to 2.628 s | | median | 0.545 s | 2.264 s | The 2 time ranges do not overlap anywhere. 0.98 s separates the slowest 1603 from the fastest 1506, the medians differ by a factor of 4, and an exact rank sum test puts a separation this clean, in the predicted direction, at about 1 in 168,000. And the slower code is exactly the set that got dispatched. So: a submitted job is validated, then dispatched. If the validator catches the undefined gate, the job dies in about half a second with **1603**. If it does not, the job is dispatched, gets a `running` timestamp, and the execution side refuses it about 2 seconds later with **1506**. Both codes are correct descriptions of what happened to that particular job. The program is identical. Which component got to it first is not. 2 limits on that model, both from my own records. It is what this circuit did on this device on this evening. And my changelog has a 1603 that arrived about 15 seconds after a job was queued, for duplicate bit arguments, on which I never read the metrics. So treat "validation stage" as what this experiment showed for this failure, not as a property of the code. The interleaving rules out a single atomic cutover: a deployment landing mid run produces 2 blocks, and 20 jobs produced 11 alternating runs. It does not rule out a rollout across replicas, although the same mixture at 17:29 and again at 20:03, 2 and a half hours apart, makes a 19 second rollout unlikely. The 2 codes do not carry the same message, and the difference is not cosmetic. The 1603 message names the cause down to the line and column: ``` Job not valid. Error loading QASM circuit with the standard Qiskit loaders: "5,0: gate 'rzz' is not defined. ``` The 1506 message does not mention `rzz`. It does not mention any gate. It offers a link to the OpenQASM guide: ``` Failed to execute program: 'Error parsing OpenQASM program: -- Pass valid OpenQASM strings that can be imported into Qiskit. ... ``` Anyone debugging a 1506 has, in that message, no way to learn that one undefined gate was the problem. Resubmitting the identical program is close to a coin flip: I saw 9 of 20, but 20 jobs put that anywhere between about a quarter and 2 thirds, so treat it as worth one retry rather than as a rate. It is not something you would guess from either message on its own. None of this destabilises the node, for a reason worth stating plainly: it never branches on `reason_code`. The check that matters runs **before** submission and warns when a circuit calls `rzz` without defining it, which is the one gate in the Heron basis that `stdgates.inc` does not define. That warning fired on all 20 submissions before any of them left my machine. The node explained the failure correctly 20 times out of 20\. IBM explained it correctly 9. ## A rejected job is not free All 20 jobs failed. Not one executed a shot: `circuits_execution_time_ns` is 0 on every one of them. The account was billed 30 seconds. 15 of the 20 carried a charge of 2 seconds, and the quota moved by exactly that. The 5 that were not charged were all validation stage failures, the ones that never got a `running` timestamp. So were 4 of the charged ones: of the 9 jobs that never reached the running phase, 5 cost nothing and 4 cost 2 seconds each. Across the whole evening, 29 jobs cost 48 seconds of an allowance that renews once every 28 days. My own repository, from the 0.4.1 audit, says no QPU time is charged for an invalid circuit. It cited `running: null` and an unchanged quota as evidence. That measurement was not even right about the case it measured: 9 jobs here have `running: null` and only 5 of them cost nothing, the other 4 were charged 2 seconds each. And it never met the 11 jobs that were dispatched and charged. The claim comes out. If you are on the Open plan, price a debugging loop accordingly. 20 attempts at a circuit that cannot possibly run is 5 percent of your month. ## Every endpoint that took whatever you typed The most consequential thing in this release has nothing to do with quantum computing. There was no `encodeURIComponent` anywhere in the package. Every endpoint that takes an identifier interpolated it straight into the request path. Confirmed live: a Job ID of `../backends` returned the device list, a Session ID of `../jobs` returned the job list, and `../../instances` escaped the `/v1` prefix entirely. The blast radius included `DELETE /jobs/{id}` and `PUT /jobs/{id}/tags`. The node is also exposed as an AI Agent tool, which means those identifiers routinely arrive from a language model rather than from a person typing into a field. Every identifier is encoded now, bounded, and validated as text before it becomes a path segment. A value of nothing but dots is refused outright, because URL resolution removes such a segment and encoding does not save you: Node decodes `%2E` before it removes dot segments. The neighbouring fix is smaller and funnier. With `jobId` empty, the request was `GET /api/v1/jobs/`, and IBM's edge answers that with its own web application: HTTP 200, 285 KB of `text/html`, handed back as a successful job status. An expression like `{{ $json.jobId }}` on an item missing the field produces exactly that. ## The fix pass wrote 4 new defects while every test stayed green Once the fixes were in, I pointed 34 agents at the change set with one instruction: break it. Every break they claimed went to a second agent whose instruction was to refute it. They found 4 documentation defects the fix pass itself had written, hours old. The README still told the reader to use Account > Set Cost Limit, an operation the same release had deleted. A screenshot caption still read "Actions (34)". Alt text still said "the nine Account actions" when there were 8\. A session finding I had corrected in the node's own field description had never reached the README. 623 tests were passing at that point. Lint was clean. Coverage was 100 percent on statements, branches, functions and lines. A test suite cannot read the README. I should say what that instrument is worth, since I am quoting its output. 34 runs against the same repository are correlated draws, not independent reviewers, and it shows: they found README text and no code defects. None of them noticed that my `rzz` control was the wrong shape, or that both error codes were sitting in a public registry. And an earlier run of the same setup died on a session limit and reported "no breaks found", which actually meant that no agent had survived long enough to report anything. A verification instrument returning a false green, in a release about verification instruments returning false greens. The best of the 20 documentation defects across this release is one the node's parameter panel showed the user twice, contradicting itself in a single screen. The operation description said Set Accepting Jobs turns acceptance on or off "without closing the session". The field description directly beneath it said the opposite. Measured on a fresh, empty batch session on `ibm_fez`: | moment | state | state\_reason | closed\_at | | ------------------------ | ------ | ---------------------- | ---------- | | after create | open | session\_created | absent | | after setAccepting false | closed | session\_closed\_early | stamped | False closes the session, true does not reopen it, and both descriptions now say that. ## The defect that lived between 2 test files IBM's Noise Learner primitive, the one that learns the noise of a circuit's entangling layers rather than sampling counts, returns a completed job under a different top level key than everything else: | primitive | top level keys in IBM's response | pubs the node found | resultsAvailable | | ------------- | -------------------------------- | ------------------- | ---------------- | | Sampler | \["metadata", "results"\] | 1 | absent | | Estimator | \["metadata", "results"\] | 1 | absent | | Noise Learner | \["data", "metadata"\] | 0 | false | My parser read `results`. A learning job that had finished successfully came back as `pubCount: 0`, `pubs: []`, `resultsAvailable: false`, and my own documentation defines that last field as meaning IBM sent no result body at all. IBM had sent 1133 bytes containing a complete `LayerError`: 15 Pauli generators and their rates. The suite covered the Noise Learner submit path in 10 places. The results file had no Noise Learner case at all, and the `resultsAvailable` tests only ever covered an empty body. The defect sat in the gap between them, on lines coverage reported as fully exercised. The gap existed because on the previous campaign I submitted a learning job and cancelled it to save quota. The submit path got proved. The result path was never reached. Decoded outside the node with numpy, to check the data was real rather than merely present, from a job on `ibm_fez`: ``` strat cz, qubits 0 and 1 ZZ 1.669e-02 <- the characteristic error of a CZ gate ZX 5.260e-03 IX 5.260e-03 XZ 3.100e-03 XI 3.100e-03 the remaining 10 generators: 0 approximate layer fidelity: 0.935 ``` A CZ gate's error profile with the ZZ term dominating, which is what it should look like. The node now emits one `noiseLearner` pub per learned layer. The rates stay base64 encoded zlib, because inflating them needs `zlib` and the n8n community node import allowlist does not include it, so decoding would fail official verification. ## The rest of 0.5.0 ![A twenty qubit circuit diagram with an X gate on every even qubit and a measurement on all twenty qubits, writing into a twenty bit classical register](https://tuguidragos.com/content/images/2026/08/circuit-da4jn8c3jnrc73agammg-2.png) The load circuit. An X on the even qubits, all twenty measured into c. Because c\[0\] is the rightmost bit, the correct reading is 01010101010101010101, not the other way round. ![The n8n Get Results node showing job da4jn8c3jnrc73agammg completed, 500,000 shots on a twenty bit register, with the alternating bitstring at 394,214 counts](https://tuguidragos.com/content/images/2026/08/n8n-local-2.png) Get Results on the 500,000 shot job. 5.25 MB of JSON from IBM, 626 distinct bitstrings, and the expected pattern sitting at 394,214 counts. **`sx` joins the palette**, taking it from 24 gates to 25, and the reason it had been left out was also mine and also wrong. I believed it had no OpenQASM 3 spelling avoiding the builtin `U`; `stdgates.inc` defines it. That matters more than one gate normally would, because `sx` is the only non-fractional single qubit rotation in the basis besides `x`. `rz` is a frame change and `id` does nothing, so without `sx` a hand written ISA circuit had to spell its Hadamard with a parametrised `rx`, which is fractional, which rules out gate twirling and the mitigation behind Estimator resilience level 2. **Every palette entry now says whether IBM runs it untouched.** 7 read "Runs as-is", 16 read "Transpile first" with the reason, 2 are special cases. 16 of 25 is most of the palette. Nothing in the dropdown said so before. A test holds those descriptions against the node's own ISA scanner, so a gate added later with the wrong blurb fails the build. **A coupling map check**, because picking gates from the basis is only half of building an ISA circuit and the qubits have to be adjacent too. It is not a naive adjacency test: `cz(3,16)` is far apart but coupled and completes, while `cz(15,16)` is consecutive but uncoupled and fails at IBM with code 1517. **Account > Set Cost Limit is gone**, leaving 33 operations rather than 34\. `PUT /v1/instances/configuration` hangs for about 180 seconds and then aborts, reproduced twice and once more with the node taken out of the picture entirely, by sending the identical request through n8n's own HTTP Request node. `GET` on the same path answers in 832 ms. IBM's OpenAPI spec flags the path as deprecated in favour of the Resource Controller API, but there is no replacement for the write, which is why the operation is gone rather than re-pointed. Set the ceiling on the IBM Cloud console; Get Configuration and Get Instance still read it back. The changelog carries the other 55 entries, including the batching, the backend dropdowns and 6 field descriptions rewritten to say what IBM measurably does. ## What I am not claiming That the split is 55 to 45 in general. It is 11 to 9 in one burst on one device on one evening, consistent with a coin flip and nowhere near enough to pin a rate. That the race is between exactly 2 components. The `running` flag and the timings show 2 paths. They do not show the internals of either, and they do not rule out a rollout across replicas. That order never matters. 6 alternating jobs, bare and defined, showed no effect: all 3 bare returned 1506 and all 3 defined completed. 3 draws cannot prove independence. What they do is remove the confound from my original observation, where a definition carrying job had run between the 2 I called identical. That the loader decomposes rather than substitutes. That experiment rules out "nothing happens" and leaves 2 live possibilities, and the control that separates them is written out above and unrun. One thing is still open in the node. A Noise Learner circuit with more than one entangling layer fails on the default settings, because the circuit builder puts a classical register in and the learner never measures your circuit. Set Number of Classical Bits to 0 and the same circuit completes with 2 learned layers. The behaviour is IBM's, the node warns at submit time, and whether the default should change is a decision I have not made. And 3 things I cannot test from this account at all: the `eu-de` region, session `dedicated` mode, which the Open plan refuses, and the Get Results timeout path, because no queue was deep enough during the campaign. ## The run in numbers | | | | --------------------- | -------------------------------------------------------------- | | jobs on real hardware | 98 across 2 days, 69 of them before the replication evening | | QPU seconds spent | 574 of the Open plan's 600 per 28 days | | the replication | 20 identical submissions in 19 seconds, 11 codes to 9 | | devices | ibm\_fez, ibm\_kingston, ibm\_marrakesh, all Heron, 156 qubits | | problems found | 26 in the product, 25 fixed, 1 open, plus 4 on IBM's side | | documentation defects | 20, 4 of them written by the fix pass itself | | tests | 669 at the end, up from 616 | | operations | 33, down from 34 | | changelog entries | 62 | ## Questions people ask ### Why did the same IBM Quantum circuit return reason\_code 1603 and then 1506? Because the 2 codes come from 2 different stages. A job is validated, then dispatched. In my runs the validator caught an undefined gate in about half a second and reported 1603, while a job that got past it was dispatched, got a `running` timestamp, and was refused about 2 seconds later with 1506\. In 20 identical submissions I got 11 1506 and 9 1603, interleaved, with every 1506 having entered the running phase and no 1603 doing so. ### What does reason\_code 1506 mean on IBM Quantum? IBM's registry titles it "QASM parsing failed". In my runs it was the rejection that arrived from the execution side after the job had been dispatched, and its message was generic: it named no gate and no line. One cause is calling a gate with no definition, such as a bare `rzz`. ### What does reason\_code 1603 mean on IBM Quantum? IBM titles it "Error loading QASM circuit with the standard Qiskit loaders". In my runs it arrived in under a second without the job ever entering the running phase, and its message named the cause down to the line and column, for example `5,0: gate 'rzz' is not defined`. It is also what you get for duplicate bit arguments, such as `cx q[0], q[0]`. ### My IBM Quantum error message says nothing useful. Should I retry? If you got 1506 and the message named no gate, it is worth one retry. The same program can be caught by the validator instead, which returns 1603 with a message that names the gate, the line and the column. I saw that in 9 of 20 submissions, which is close enough to a coin flip that I would not plan around a rate. Budget for it: each attempt can cost about 2 seconds of QPU allowance even though nothing runs. ### Can I use rzz in an OpenQASM 3 circuit on IBM hardware? Yes, if you ship the definition with it. `rzz` is in the Heron basis set but `stdgates.inc` does not define it, so a bare `rzz(0.5) q[0], q[1];` is refused before the target is consulted. Include `gate rzz(p0) a, b { cx a, b; rz(p0) b; cx a, b; }` and the same call completes. **This corrects the answer in an earlier post, which said rzz could not be used through the REST API.** What I can say is that the job completes. Whether the hardware runs the native RZZ or the `cx, rz, cx` body mapped onto the basis, I could not determine. Note also that `rzz` is a fractional gate: Heron only, angles from 0 to π/2, experimental, and incompatible with Pauli twirling, PEC, probabilistic error cancellation, and ZNE, zero noise extrapolation by error amplification. ### Do failed IBM Quantum jobs use up my monthly quota? Yes, including jobs that never execute a shot. 15 of my 20 rejected circuits were charged 2 seconds each, 11 of them jobs that had been dispatched and 4 that died in validation without ever running; the other 5 validation failures were charged nothing. Together they cost 30 seconds of my Open plan allowance while executing 0 shots. ### Does Qiskit Runtime transpile my circuit for me? Not in the way that would save you. It will not route your qubits onto a coupling map, and it will accept a circuit it cannot run, queue it, and fail it seconds or minutes later depending on the queue. But it does not hand your program to the hardware untouched either: 3 instructions inside a `gate rzz` definition run, while the identical 3 written out flat are refused with code 1517 because `cx` is not in the basis. ### What are the basis gates on IBM Heron processors? The REST API reports `cz, id, rx, rz, rzz, sx, x`, identical on all 3 devices I can reach, plus measure, reset, delay and barrier. Be careful with that list, because it is the fractional inclusive superset. Qiskit gives you a narrower conventional set, `cz, x, rz, sx, id`, unless you load the backend with `use_fractional_gates=True`, so a gate set taken from the API can hand you fractional gates you never chose. 2 further caveats that cost me jobs: `rzz` needs a definition block, and `id` is listed but the node emits nothing for it, because `stdgates.inc` defines it as `U(0, 0, 0)` and a job emitting that builtin fails on the target. Why `x`, also defined through `U` in the same file, resolves fine is something I have not established. ### Why does a completed IBM Noise Learner job return zero pubs? Because Noise Learner returns its payload under `data` while Sampler and Estimator return theirs under `results`. A parser reading only `results` reports 0 pubs for a job IBM answered in full. Fixed in `n8n-nodes-ibm-quantum` 0.5.0. ## The control, not the calendar The tidy version of this post is that measurements have a shelf life and mine went stale. It would have been a nicer ending and it is not true. A bare `rzz` failed on 2 August and a bare `rzz` failed 20 times on 22 August. Nothing decayed. IBM did not change anything underneath me. What was wrong was never the measurement. It was the inference I drew from one observation, using a control that could only agree with me, and then a second inference from 2 observations that called a mechanism random because I had not looked at it long enough to see the shape. The thing that separated them took 19 seconds and 30 seconds of quota. [The last post about the node](https://tuguidragos.com/six-defects-verified-n8n-node-ibm-quantum/) ended on the line that every defect came from trusting a layer: green tests, covered lines, a vendor's gate list. This one has a narrower and more embarrassing version. 1 job is an observation. 2 jobs are a coincidence. 20 jobs, submitted with nothing in between and read against their own timestamps, is a measurement. I had been shipping on observations and calling the surprises nondeterminism. ## Where to find it The node is `n8n-nodes-ibm-quantum` on [npm](https://www.npmjs.com/package/n8n-nodes-ibm-quantum), MIT licensed, and it installs from the editor canvas on n8n Cloud and self hosted. The [source and the full changelog](https://github.com/TuguiDragos/n8n-nodes-ibm-quantum) are on GitHub, along with the verification report and the raw replication data behind this post: every job ID, timestamp, code and message, so you can check the split yourself. ### The tensor product: why 10 qubits means 1024 numbers URL: https://tuguidragos.com/the-tensor-product-why-10-qubits-means-1024-numbers/ Last updated: 2026-09-13T13:05:00.000Z You hear it constantly. Quantum computers are exponentially powerful. The state space doubles with every qubit. It is the line that makes quantum sound like magic and makes you feel like you are missing something. You are not missing much. The exponential comes from one operation, the tensor product, and it is far simpler than the word suggests. Combine two qubits and you get four numbers. Here is exactly how, by hand. ## Start with the counting One qubit is two numbers. The amplitude on |0⟩ and the amplitude on |1⟩, the pair from [the very first posts](https://tuguidragos.com/a-qubit-is-not-a-coin-under-a-cup/). Two amplitudes, and [squaring their lengths gives the probabilities](https://tuguidragos.com/measurement-is-not-magic-the-born-rule-by-hand/). Two qubits is four numbers. The possible outcomes are 00, 01, 10, and 11, and there is an amplitude on each, so four amplitudes describe the pair. Three qubits is eight, the outcomes running 000 through 111\. You can already feel the pattern, and the operation that builds the bigger list out of the smaller ones is the tensor product. ## How to actually compute it The tensor product takes two vectors and builds a longer one by a simple, mechanical rule: multiply each entry of the first by the whole of the second, and stack the results. For two qubits, \[a, b\] tensored with \[c, d\] gives \[ac, ad, bc, bd\]. NumPy spells the tensor product kron, for Kronecker product, which is the same thing. ```python import numpy as np print(np.kron([1, 0], [1, 0])) # [1 0 0 0] -> |00> print(np.kron([1, 0], [0, 1])) # [0 1 0 0] -> |01> ``` Run it. The state |0⟩ tensored with |0⟩ is \[1, 0, 0, 0\], a vector of length four with all its weight on the first slot, which is the |00⟩ outcome. |0⟩ tensored with |1⟩ puts the weight on the second slot, |01⟩. The four slots of the combined vector are exactly the four outcomes 00, 01, 10, 11, in order. Try it with a superposition and nothing changes about the mechanism. ```python plus = np.array([1, 1]) / np.sqrt(2) # |+> print(np.round(np.kron(plus, [1, 0]), 3)) # [0.707 0 0.707 0] ``` Run it. |+⟩ tensored with |0⟩ spreads its weight across the 00 and 10 slots, which says the first qubit is undecided and the second is firmly 0, exactly what you put in. The tensor product is just bookkeeping for combining independent qubits into one description, and you can do it on paper for two qubits without strain. ## Three qubits, and which slot is which The operation chains. For three qubits you tensor three vectors, left to right, and the result has eight slots. ```python k = np.kron(np.kron([1, 0], [1, 0]), [1, 0]) print(k, "length", len(k)) # [1 0 0 0 0 0 0 0] length 8 ``` Run it. Three copies of |0⟩ tensor to |000⟩, a length eight vector with all its weight on the first slot. And here is the indexing rule that makes a long amplitude vector readable, because it confused me at first. The slots are just the outcomes counted in binary. Slot 0 is 000, slot 1 is 001, slot 2 is 010, and so on up to slot 7, which is 111\. So the position in the vector is the binary number of the outcome it represents. Once you see that, a vector of sixteen amplitudes for four qubits stops being an intimidating wall of numbers and becomes an ordered list: slot five is outcome 0101, and the length of its amplitude, squared, is the probability of measuring 0101\. The tensor product lays the outcomes out in plain binary order, every time, which is the thing that lets you actually read these vectors instead of drowning in them. ## The doubling, and where the reputation comes from Now watch the counts as you add qubits, because this is the whole point. ```python for n in [1, 2, 3, 10, 20]: print(f"{n} qubits -> {2**n} amplitudes") ``` Run it. One qubit, two amplitudes. Two qubits, four. Three, eight. Ten qubits, one thousand and twenty four numbers. Twenty qubits, over a million. Each qubit you add doubles the length of the vector, because tensoring with one more two slot qubit doubles the slots. n qubits is described by 2 to the power n complex numbers. That is the exponential everyone keeps pointing at, and it is not a vague slogan, it is just this doubling, compounding. The numbers get absurd fast. At fifty qubits you are past a quadrillion amplitudes. At three hundred qubits, the count of amplitudes exceeds the number of atoms in the observable universe. A modest pile of qubits describes a state living in a space so large you could never write it down. That enormous space, all of it evolving together when you apply a gate, is the arena quantum computing plays in. That is where the power is supposed to come from. ## The same exponential is also the wall Here is the part the hype usually skips, and it is the most useful thing in this post. The exponential that makes quantum powerful is the exact same exponential that makes quantum hard to fake. To simulate n qubits on an ordinary computer, you have to store those 2 to the n amplitudes in memory and update them. Thirty qubits is already a billion numbers, heavy but doable on a big machine. Around fifty qubits you blow past what any classical computer on Earth can hold, because each extra qubit doubles the memory and the doubling wins, always and quickly. This is why you cannot just simulate a large quantum computer on a laptop, or a supercomputer, or all the supercomputers together. The state does not fit. And it is the deepest reason real quantum hardware matters: a physical qubit holds its share of that exponential state for free, as a matter of physics, where a classical simulation has to pay for every amplitude in memory it does not have. The doubling is the promise and the wall at once. Respect it from both sides. Put real numbers on it to feel the wall. Each amplitude is a complex number, call it sixteen bytes. Fifty qubits is 2 to the fifty amplitudes, which is over a quadrillion, times sixteen bytes, which lands around sixteen petabytes of memory just to hold the state for an instant, before you have done a single operation on it. Sixty qubits is another thousandfold on top of that. There is no cluster you can rent that makes this go away, because the problem is not engineering, it is the exponent, and you cannot out build an exponent. Every qubit added does not make the simulation a bit harder. It doubles it. That cliff, sitting somewhere around fifty qubits, is precisely the line past which no honest state vector simulation can follow. Specialised methods can still reach past it on circuits with enough structure to exploit, sometimes exactly, by never storing the whole vector at all. None of them repeal the general case, and none of them move the memory wall. ## Gates scale by tensoring too One loose end, because it ties back to [the gates post](https://tuguidragos.com/quantum-gates-are-just-matrices-you-can-run-by-hand/). When you have several qubits and you apply a gate to just one of them, that gate is also a tensor product. A Hadamard on the left qubit of a two qubit system is really H tensored with the identity on the right one, because the untouched qubit gets the do nothing matrix. The result is a bigger matrix that acts on the whole combined vector. ```python H = np.array([[1, 1], [1, -1]]) / np.sqrt(2) I = np.eye(2) print(np.kron(H, I).shape) # (4, 4) ``` Run it. H tensored with I is a four by four matrix, the right size to multiply the four slot state of two qubits. So the entire picture from the gates post, apply a gate by multiplying a matrix, scales up cleanly: act on one qubit by tensoring its gate with identities on the rest, and the mechanism is unchanged, just larger. Everything is still matrix times vector. The vectors and matrices simply grow with the tensor product. And tensoring two real gates together pays off immediately. Put a Hadamard on each of two qubits, both starting in |0⟩, and watch what comes out. ```python HH = np.kron(H, H) print(np.round(HH @ np.array([1, 0, 0, 0]), 3)) # [0.5 0.5 0.5 0.5] ``` Run it. H tensored with H, applied to |00⟩, gives \[0.5, 0.5, 0.5, 0.5\], equal amplitude on all four outcomes. Two Hadamards just put two qubits into superposition at once, and the tensor product turned that into a single state spread evenly over 00, 01, 10, 11\. Scale this up and it is the move behind a huge amount of quantum computing: a Hadamard on every one of n qubits produces an equal superposition over all 2 to the n outcomes in one layer of gates. That is how an algorithm gets the entire input space into play simultaneously, and it is nothing but the tensor product doing its doubling, gate side this time instead of state side. ## The entanglement connection This is also where [the entanglement post](https://tuguidragos.com/entanglement-explained-without-the-spooky/) and this one meet. The tensor product builds the big combined space by gluing independent qubits together. A state that was built that way, by tensoring separate qubits, is a product state, and it always factors back into its parts. But the combined space, all 2 to the n slots of it, contains far more vectors than just the ones you can build by tensoring. Most of the vectors in there cannot be factored into separate qubit states at all. Those are the entangled states. The Bell state from the entanglement post is one of them, a four slot vector that no tensor product of two qubits can produce. So the tensor product builds the room, and entanglement is everything in the room that cannot be taken back apart. The two ideas are two halves of the same picture. ## Order matters: which qubit goes where One practical wrinkle, because it connects to a warning from earlier posts. The tensor product is not symmetric. |0⟩ tensored with |1⟩ is \[0, 1, 0, 0\], the outcome 01, while |1⟩ tensored with |0⟩ is \[0, 0, 1, 0\], the outcome 10\. Same two qubits, opposite order, different state. So when you combine qubits, you have to keep track of which one is on the left and which on the right, because the order decides which slot each outcome lands in. This is the same little endian issue that the [Python](https://tuguidragos.com/the-python-you-actually-need-for-quantum-computing/) and gates posts warned about. Qiskit orders its qubits in the reverse of the textbook convention, qubit 0 on the right of the string, so a state your hand calculation labels one way may be labelled the mirror way by the library. Neither is wrong, they are conventions, but if you tensor in one order and the tool reports in the other, you will stare at a vector that looks scrambled until you remember the order is flipped. The fix is just awareness: pick a convention, know which one your tool uses, and the slots line up. The tensor product is mechanical, but it is not order blind, and quietly assuming it is costs you an afternoon of confusion you do not need to have. ## The difference from classical bits, which is the whole point There is a confusion worth heading off, because it tripped me and it hides the real source of the power. You might object that classical bits also have 2 to the n possible states. Eight bits can represent any of two hundred and fifty six values, ten bits a thousand and twenty four, the same exponential. So what is special? The difference is everything. With n classical bits, there are 2 to the n possible states, but at any moment you hold exactly one of them. Ten bits store one number out of a thousand, the one they happen to be set to. The exponential is just the size of the menu, and you are only ever eating one dish. With n qubits, the state is not one of the 2 to the n possibilities, it is an amplitude on all of them at once, the full vector live and present together. Ten qubits do not hold one of a thousand numbers. They hold a thousand and twenty four amplitudes simultaneously, every outcome carrying its own complex weight, all of them there at the same time. That is the gap classical intuition misses. The classical machine has an exponential number of possible configurations and occupies one. The quantum machine occupies an object that has an exponential number of parts. When you apply a gate, you act on the entire vector in one go, nudging all 2 to the n amplitudes together, which is the closest honest description of where the much abused phrase massive parallelism comes from. The catch, and there is always a catch, is that measurement still hands you back just one outcome, so the art of a quantum algorithm is steering that giant vector with interference so that the outcome you want is the one likely to appear. The exponential state is real and present. Reading it out is the part you have to be clever about. ## A quick test before you move on Close this and answer in your own words. How many complex numbers describe the state of eight qubits? If you did not immediately think two to the eighth, which is two hundred and fifty six, the doubling has not become automatic yet, so go back to the counting. What is the actual rule for tensoring \[a, b\] with \[c, d\]? If you cannot produce \[ac, ad, bc, bd\], reread the how to compute it section, because that mechanical step is the whole operation. And why can you not store the state of a fifty qubit quantum computer on a classical machine? If your answer does not mention that the memory doubles per qubit, the section on the wall did not land. ## Where I am learning it Free. For the linear algebra underneath, 3Blue1Brown is again the clearest on what these vectors and matrices actually are, and any introduction to the Kronecker product fills in the mechanics. The exponential blowup is one of those things that is obvious once you compute it and abstract until you do, so the most useful exercise was the tiny loop above, watching the amplitude count double and double until the numbers stopped meaning anything. As always, I keep these as NumPy I can rerun, because a doubling I have only read about does not land like a doubling I have watched run off the end of what any machine could hold. ## One humble operation For a long time the exponential power of quantum sat in my head as a kind of slogan, something true that I could repeat without really feeling. It turned out to rest on one of the plainest operations in the whole subject, a bookkeeping rule for combining vectors that you can do by hand for two qubits in a few seconds. The tensor product is not where the difficulty lives. The difficulty lives in what it implies, the relentless doubling that builds a state space too big to write down, powering the machine on one side and defeating every attempt to simulate it on the other. The operation is humble. The consequence is the entire reason the field exists. Worth knowing which is which. ### I could not find a place to practise Qiskit, so I built one URL: https://tuguidragos.com/i-built-a-place-to-practise-qiskit/ Last updated: 2026-09-13T17:34:43.000Z For the last few weeks I have been looking for a workbook. Not a tutorial, and not documentation. A workbook. Somewhere I could open a file, write an answer, run one command, and be told whether the answer was right. The thing that exists for almost every other subject I have ever tried to learn. I did not find one. There is no shortage of Qiskit material. IBM's documentation is a reference, and a very good one. There are textbooks that teach the theory properly. There are hundreds of tutorials. What I could not find was the part in the middle: a set of exercises that run against the Qiskit that ships today, and that check your answer instead of showing you theirs. So I built one. Twenty exercises in four acts, from an empty laptop to a Bell state on a real IBM machine, and then to the inequality that no shared coin can reach. It is called `quantum-exercises`, it is MIT licensed, and it lives on [GitHub](https://github.com/TuguiDragos/quantum-exercises) and [PyPI](https://pypi.org/project/quantum-exercises/) if you would rather go straight there. Writing the exercises turned out to be the easy half. ## Every tutorial I found was written for software that no longer exists In February 2024, Qiskit 1.0 removed a large part of the interface that every piece of teaching material had been built on. **`execute()` is gone.** It was the centre of every tutorial: build a circuit, call it, get results. It has two replacements now, `transpile` followed by `backend.run`, or the primitives. **`Aer` left the `qiskit` package.** The line `from qiskit import Aer` is an `ImportError` today. It lives in `qiskit-aer` now. **`IBMQ` no longer exists.** It became `QiskitRuntimeService`, with a completely different authentication model. **The shape of a result changed.** With the primitives, counts moved to `result[0].data..get_counts()`, a level of indexing that was not there before. The lower level path still returns a `Result` that has `get_counts()` on it, and almost every tutorial gets that distinction wrong in one direction or the other. I wrote about [what broke and what replaced it](https://tuguidragos.com/the-version-trap-that-cost-me-an-evening-qiskit-2-x/) after it cost me an evening, so I will not repeat that here. The technical consequence is boring: old code does not run. The human consequence is not boring at all. A beginner following a tutorial from 2021 hits an `ImportError` on line three. They have no way of knowing the instructions are wrong rather than them. The conclusion most people draw is that they are not clever enough for quantum computing, when what actually happened is that they were reading instructions for software that no longer exists. That is the observation the whole thing is built on. It is also why exercise 13 is called "Code from 2021 that no longer runs" and teaches migration explicitly. It is the one skill that unlocks every old tutorial you will ever find. ## The nearest things exist, and neither one is a workbook The obvious question is whether somebody has already done this, and the honest answer is that two projects come close. The first is Microsoft's Quantum Katas. The original repository was archived by its owner on 21 August 2024 and is read only, which is easy to find and easy to misread. The Katas were not abandoned. The experience was rebuilt and now lives at quantum.microsoft.com, with the modern kit in Microsoft's `qsharp` repository. It teaches Q#, a different language on a different stack. The second is much closer, and I did not know it existed until somebody pointed me at it. In May 2026, Juan Cruz-Benito and Ismael Faro at IBM Research published Qiskit QuantumKatas: a port of those same exercises from Q# to Qiskit, 350 tasks across 26 categories, each with a prompt, a canonical solution and deterministic verification. So somebody did build the Katas for Qiskit, and it would be dishonest of me to claim the field was empty. It is aimed at a different reader. It is a benchmark for evaluating large language models on quantum code generation, shipped as a dataset, and the thing being measured is a model rather than a person. There is no install, no progression, and no feedback written for somebody who is stuck, because none of that would serve what it is for. So the gap I could not find filled is narrower than "nobody built this", and the precise version is the only one worth stating: a local, self checking beginner workbook for the Qiskit that ships today, with feedback written in the language of the concept you tripped over, and an optional path onto real hardware. ## I do not know enough to guarantee the physics myself Here is the problem with what I have just described. This site says plainly that I am learning quantum computing. I am not a physicist. I do not have the standing to publish twenty exercises and ask you to trust that the physics in them is right because I say so. So I did not. What I built instead turns every assumption behind a lesson into something executable. Every reference solution is run. Every diagnostic an exercise promises is triggered on purpose and asserted, so a message that stops being accurate breaks a test instead of quietly misleading somebody. The suite runs against the current release of Qiskit on a schedule, so a version that breaks an exercise surfaces on the repository rather than in your terminal. Be clear about what that does and does not buy. It does not certify the physics independently. If an assumption of mine is wrong, a test built on that assumption enshrines the error rather than catches it. What it does is make every assumption explicit, executable and open to anybody who wants to check it, and it catches the far more common failure, which is drift: material that was right when it was written and stopped being right when the library moved underneath it. That is a weaker claim than "these exercises are correct". It is also one I can stand behind, and expert eyes on the checkers are welcome. Right now that machinery is 1,131 tests, at 100 percent line and branch coverage of the tool, with the threshold set to fail below 100\. That coverage figure is about the code that does the checking, not about the exercises themselves. One test is gated behind a real hardware account and does not run by default. The rest run on Python 3.10 through 3.14. ## Comparing text is the wrong way to check a quantum answer The naive way to check an exercise is to compare the learner's output to the reference output. For quantum code that is wrong twice over, and both reasons are interesting. The first is global phase. The states |ψ⟩ and e^(iθ)|ψ⟩ are [the same physical state](https://tuguidragos.com/complex-numbers-are-the-gateway-to-quantum/). No experiment can tell them apart, ever. It is not that the difference is small, it is that the difference is not observable in principle. A learner who prepares the right state with the phase turned by π has not made a mistake, because there is nothing there to be mistaken about. So states are compared with `Statevector.equiv`, which ignores global phase, and operators with `Operator.equiv`, for the same reason. Punishing somebody for a quantity that does not exist would be a strange way to teach them physics. The second reason is that there is rarely one right circuit. Exercise 08 asks you to build a controlled Z out of nothing but Hadamards and a CNOT. There are several orders that work. Comparing your code to mine would reward the one I happened to write. Comparing the operator your code produces to the operator the exercise wants rewards all of them. ```python # checks.py, simplified from qiskit.quantum_info import Operator, Statevector ATOL = 1e-6 def assert_state(actual, target): if not Statevector(actual).equiv(Statevector(target), atol=ATOL): raise CheckFailed("Your circuit does not prepare the target state.") def assert_operator(actual, target): if not Operator(actual).equiv(Operator(target), atol=ATOL): raise CheckFailed("Your circuit is not the target operator.") ``` The tolerance is 1e-6, looser than the Qiskit default of 1e-8, so that an honest answer assembled in a slightly different gate order still passes. There is no correct solution. There is a correct operator. If the reason gates behave like matrices at all is not obvious to you, I wrote about [why they are matrices you can multiply by hand](https://tuguidragos.com/quantum-gates-are-just-matrices-you-can-run-by-hand/). ## Sampling is random, so equality is the wrong test Measurement results are worse than ambiguous. They are random. Run the same correct circuit twice and you get different counts. Any check that compares counts for equality is not strict, it is simply wrong, and it will fail correct answers on a schedule set by chance. So counts are never compared for equality. Three things happen instead. **Proportions are tested against the binomial standard error.** For an outcome expected with probability p over N shots, `SE = sqrt(p(1-p)/N)`, and the observed proportion has to fall within four standard errors. That threshold gives roughly a 6.3 in 100,000 chance of failing a correct answer, per assertion, which is low enough that the suite does not flake. **Whole distributions go through a chi square goodness of fit test** at a significance level of 0.001. **Support is checked only where the physics forbids an outcome outright**, meaning which results appear at all rather than how often. Never on hardware, where noise puts a few shots almost anywhere. There is a detail here that took me a while to appreciate. For a two outcome distribution, the chi square statistic is exactly z squared, so the 3.3 standard error threshold coming out of the chi square test and the 4 sigma threshold from the proportion test are consistent with each other rather than two arbitrary numbers. Exercise 06 explains that to the learner, next to [the Born rule worked out on paper first](https://tuguidragos.com/measurement-is-not-magic-the-born-rule-by-hand/). The consequence of all of this is the property I actually wanted. An answer that is right for reasons I did not anticipate passes. An answer that only looks right does not. ## One exercise accepted an answer that never asked the question That property failed once, and finding out how it failed was the most interesting hour of the build. Exercise 10 is Deutsch's algorithm. You are handed an oracle and you have to determine, in a single query, whether the function it encodes is constant or balanced. The whole point is the single query. There is a way to cheat. Read the oracle's matrix, work out by hand which of the four possible functions it is, and return the answer without ever putting the oracle in a circuit. The check accepted it. My first conclusion was that this could not be fixed, and I had an argument for it: for two of the four oracles, the circuit a cheat produces is identical to the honest one, bit for bit. If the outputs are the same, no inspection of the output can separate them. The argument was wrong, and I only saw why when I was asked to look again. I had quietly assumed the check could only inspect the output. It also controls the input. The fix is to hand the function an oracle from outside the set of four: a T gate, which is not a phase oracle for any boolean function at all, since a phase oracle only ever flips the sign of the inputs where its function is 1\. Code that composes whatever it is given still works. Code that classifies the matrix against a table of four known cases falls over. I tested it against eight implementations. Three honest styles pass, five varieties of cheating and failure are rejected cleanly. **When you cannot tell two behaviours apart by their output, change the input.** That generalises well beyond quantum computing, and it is the single thing from this project I expect to reuse most. Two holes remain, and both are worth stating rather than hiding. The probe proves that the oracle reaches the circuit. It does not count queries. A solution that applies the oracle nine times still passes, because each of the four real oracles is its own inverse, so an odd number of applications is indistinguishable from one, and T to the ninth power is T again. Nobody writes that by accident and it gains you nothing, but a single query is the claim the algorithm makes, and the checker does not enforce it yet. The second is narrower. An implementation written specifically to detect the probe, hardcoding the four known matrices and composing correctly for anything else, also passes. I verified that. But I verified the part that matters too: for the four real oracles, the circuit it produces is physically identical to the honest one. What stays undetectable there is exactly what is unobservable. ## A Bell state on a real machine, on 11 August 2026 Exercise 14 is where the simulator stops. You transpile a Bell pair to a backend's actual instruction set and you send it to a QPU. ![Exercise 14 run on ibm_marrakesh, a real IBM QPU. The queue question is answered yes, then the histogram shows 1024 shots: 00 at 49.0 percent, 11 at 48.4 percent, and 01 and 10 together at 2.54 percent. The summary reports the circuit as submitted, its ISA form, and that the disagreeing shots are noise rather than a bug.](https://tuguidragos.com/content/images/2026/08/08-real-hardware-1.png) Exercise 14 on ibm\_marrakesh, 11 August 2026\. The 01 and 10 rows are outcomes an ideal Bell state cannot produce. Nothing reaches hardware without somebody answering a question first. The tool asks IBM which QPU is free, tells you how many jobs are ahead of you, and waits. If there is no terminal to answer in, so a script, a CI job or an editor task, the answer is no and it says why. Watch mode always answers no, because it runs again on every save and asks nothing. If the queue cannot be read, nothing is sent. Here is the run from the screenshot above. 1,024 shots on `ibm_marrakesh`, on 11 August 2026: **00 appeared 502 times and 11 appeared 496 times.** Together that is 998 shots out of 1,024, or 97.46 percent, and those are the two outcomes an ideal Bell state is allowed to produce. **01 appeared 14 times and 10 appeared 12 times.** Together, 26 shots, 2.54 percent. Theory forbids both of them. The machine produced them anyway. Those 26 shots sit where the device's published error rates say they should, rather than where a broken circuit would put them, and the runner says so on screen rather than leaving you to worry about it. They are the subject of the next exercise, which is about reading a noisy result honestly instead of assuming you broke something. If the reason the allowed outcomes are correlated at all is not settled in your head, I wrote about [entanglement without the spooky part](https://tuguidragos.com/entanglement-explained-without-the-spooky/). ![The IBM Quantum Platform workload page for job d9tmg21dsedc73aj5ma0, showing a completed sampler job on ibm_marrakesh, created and finished on 11 August 2026, with eleven seconds total completion time and two seconds of QPU usage.](https://tuguidragos.com/content/images/2026/08/ibm_marrakesh-1.png) A screenshot of my own terminal is not evidence. This is the job record behind it, on IBM's side. Three durations appear in those two pictures and they are not the same thing. The command took 21.03 seconds end to end. The job took 11 seconds at IBM. The QPU itself was busy for 2 seconds. On the open plan, where the budget is 10 minutes per rolling 28 days, the difference between those numbers is worth understanding before you spend any of it. The transpilation is visible in the summary too. The circuit goes in as one Hadamard, one CNOT and two measurements. It comes out as six `rz`, three `sx`, one `cz`, two measurements and a barrier, because that is the instruction set `ibm_marrakesh` exposes. Not all of those are things the machine does. A barrier is a directive to the compiler, and on this hardware an `rz` is usually a frame change rather than a pulse, which is to say it takes no time at all. This is the same wall I ran into the first time I [put a circuit on IBM hardware from an automation workflow](https://tuguidragos.com/quantum-circuits-ibm-hardware-n8n/). ## The course checks itself on the 1st and the 15th The obvious objection to everything above is that Qiskit will change again, and then this becomes one more set of exercises that no longer runs. That objection is correct, and it is the reason the project exists, so it would be absurd not to answer it. A scheduled job answers exactly one question: does this course still work with the Qiskit that exists today? It has four parts, and they do not all run on the same day. **Latest** ignores the lockfile on purpose and resolves the newest versions the ranges allow. It is the only job that can see a change out in the world, and it runs on both the 1st and the 15th. **Preview** installs above the major version ceiling, because the release most likely to break something is precisely the one Latest is not permitted to see. It runs twice a month as well, and it reports without failing the workflow, because the status badge on the repository answers whether the shipped course works and the shipped course still pins below the next major. **Locked** installs the exact pinned versions on Linux, macOS and Windows. The 1st only, because nothing it tests changes inside a month and it costs more than the rest put together. **Wheel** builds the package and walks a learner through installing it, on all three operating systems. The 1st only, and by hand before a release worth being sure about. Note what it is not: it builds from the repository rather than downloading what is on PyPI, so it tests the packaging rather than the artefact somebody already has. The honest version of what this buys is narrower than "nothing can break". A Qiskit release can land on the 2nd and go unseen until the 15th. What the schedule removes is the exact failure this project exists because of, which is material that quietly stops running while nobody finds out for two years. This is the same discipline that turned up [six defects in my n8n node](https://tuguidragos.com/six-defects-verified-n8n-node-ibm-quantum/) after I had already shipped it, applied to a different kind of artefact. ## This is an on ramp, not a curriculum Twenty exercises stop well short of quantum computing. There is no Grover, no Shor, no VQE or QAOA, no error mitigation beyond correcting readout by hand, and no error correction at all. Act IV reaches expectation values and the CHSH inequality, the numerical bound a Bell test has to beat, and then it ends. If you want a full course, this is not one. What it is meant to do is get you from an empty laptop to the point where the rest of the material becomes readable. You need to be able to write basic Python, and [not very much of it](https://tuguidragos.com/the-python-you-actually-need-for-quantum-computing/). You need no quantum background and no linear algebra beyond multiplying a small matrix by a vector, and where a matrix shows up, the runner prints it. I will add exercises as I learn the material well enough to be checked on it. I am not going to promise a schedule, because I have a physically demanding day job and a linter, qxlint, that is only at 0.1\. What I will promise is narrower and I can keep it: anything I add goes through the same checking as everything already there, or it does not go in. One more design note, visible in the screenshot at the top of this post. One row reads `solved` rather than `done`. That is what the tool records when the answer was revealed rather than found. `qx solution` prints the answer to anybody who asks for it, because a system built to stop you cheating yourself is a system built on a misunderstanding of who it is for. It just keeps the distinction visible instead of flattening it into a green tick. ## Where it lives It is on PyPI and on GitHub, MIT licensed. ```bash uv tool install quantum-exercises qx init cd quantum-exercises qx doctor qx next ``` `pipx` and `pip` work too. An IBM Quantum account is optional: every exercise runs on a local simulator without one, and the only exercise that wants real hardware degrades to a noise model copied from a real device, and tells you that is what happened. The repository is at [github.com/TuguiDragos/quantum-exercises](https://github.com/TuguiDragos/quantum-exercises) and the package at [pypi.org/project/quantum-exercises](https://pypi.org/project/quantum-exercises/). If something breaks, or an error message leaves you stuck, [the issue tracker](https://github.com/TuguiDragos/quantum-exercises/issues) is the useful place to put it. There are fourteen rules that turn common Qiskit exceptions into plain language, and every one of them was written by triggering the real error rather than guessing at the wording. The rules only cover the mistakes I could anticipate. The long tail is the part I cannot, and when nothing matches, the tool says so and calls it its own gap rather than yours. That is the whole thing. I could not find a workbook, so there is one now. ### Entanglement, explained without the spooky URL: https://tuguidragos.com/entanglement-explained-without-the-spooky/ Last updated: 2026-10-03T13:34:22.000Z Entanglement gets the full mystical treatment. Spooky action at a distance, the phrase Einstein used as an insult and the internet adopted as a slogan. Particles mysteriously linked across the galaxy. Telepathy, in the bad science fiction. It is presented as the strangest, least comprehensible thing in physics, the part where you are supposed to give up and just accept the magic. It is a correlation. You can build one in two gates and measure it yourself this afternoon. There is something genuinely surprising at the bottom of it, and I will get to that honestly, but it is not what the marketing says, nothing crosses the gap that you could ever use, and it is definitely not telepathy. Here is what entanglement actually is. ## The two line recipe You make an entangled pair with two gates. A Hadamard on the first qubit, then a CNOT from the first to the second. ```python from qiskit import QuantumCircuit from qiskit.quantum_info import Statevector import numpy as np qc = QuantumCircuit(2) qc.h(0) # qubit 0 into superposition qc.cx(0, 1) # CNOT: flip qubit 1 if qubit 0 is 1 print(np.round(Statevector(qc).data, 3)) # amplitude 0.707 on |00> and |11>, zero elsewhere ``` Run it. The state vector is \[0.707, 0, 0, 0.707\], and reading those four slots as the amplitudes on |00⟩, |01⟩, |10⟩, and |11⟩, that is (|00⟩ + |11⟩) divided by √2\. That object has a name, the Bell state, and it is the simplest entangled state there is. Read what it says. There is an equal amplitude on |00⟩ and on |11⟩, and exactly zero amplitude on |01⟩ and |10⟩. The two qubits are in a [superposition](https://tuguidragos.com/a-qubit-is-not-a-coin-under-a-cup/) of both being 0 and both being 1, with nothing in between. ## What it means when you measure The amplitudes tell you the measurement story before you run it, by [the same rule as always](https://tuguidragos.com/measurement-is-not-magic-the-born-rule-by-hand/): square them and you have the probabilities. Equal weight on 00 and 11, none on the mixed outcomes. So if you measure both qubits, you should get 00 or 11, each about half the time, and never 01 or 10\. Let us check. ```python from qiskit.primitives import StatevectorSampler m = QuantumCircuit(2, 2) m.h(0); m.cx(0, 1) m.measure([0, 1], [0, 1]) counts = StatevectorSampler().run([m], shots=2000).result()[0].data.c.get_counts() print(counts) # {'00': ~1000, '11': ~1000} ``` Run it. Only 00 and 11 come back, in a roughly even split, and the mixed outcomes never appear. The two qubits always agree. Measure one and find 0, the other is 0\. Find 1, the other is 1\. Every single time. That perfect agreement is the first thing the entangled state buys you. Be careful with it, though, because agreement in this one basis is a consequence of entanglement and not proof of it. A few sections from now I will describe something completely ordinary that produces the same table. What entanglement actually is needs a sharper definition than this, and that is what comes next. ## The precise meaning: the whole is defined, the parts are not Here is the definition that finally made it click for me, and it is sharper than spooky. A state is entangled when you cannot describe the two qubits separately. You cannot say qubit A is in this state and qubit B is in that state, and combine them. The only honest description is of the pair as a whole. Contrast it with an unentangled pair. If qubit A is in some state and qubit B is in some state, independently, you build the joint state with the tensor product, the operation that glues two single qubits into one combined description, and you can always take it back apart into its two halves. That is a product state, and the parts have their own identities. The Bell state cannot be taken apart that way. There is no pair of single qubit states whose tensor product gives you (|00⟩ + |11⟩) over √2\. Try to find one and you will fail, because it does not exist. The information is not in qubit A, and not in qubit B. It is in the relationship between them, and nowhere else. ## Each qubit alone looks like pure noise This is the part that makes it concrete. If you ignore one qubit of the pair and look only at the other, what do you see? Pure randomness. Fifty fifty, no structure at all. ```python from qiskit.quantum_info import partial_trace sv = Statevector(qc) reduced = partial_trace(sv, [1]) # throw away qubit 1, look at qubit 0 alone print(np.round(np.real(reduced.data.diagonal()), 3)) # [0.5 0.5] ``` Run it. What comes back is the description of one qubit on its own, with the other discarded, and the name for that is the marginal. It is [a density matrix rather than a state vector](https://tuguidragos.com/the-density-matrix-what-the-state-vector-misses/), which is precisely because half an entangled pair has no state vector of its own to give you. Its diagonal holds the probabilities, and here they are fifty fifty. Qubit 0, on its own, is a perfect coin. So is qubit 1\. Neither qubit carries any information by itself. All of it lives in the correlation, in the fact that whatever the coins do, they do it together. This is exactly why you cannot read anything out of one half of an entangled pair. There is nothing there to read. The structure only appears when you bring the two measurements together and compare. ## Why this is not faster than light communication Now to kill the science fiction, carefully, because it is a fair question. The tempting way to put it is that measuring qubit A here instantly determines what qubit B will give over there, however far apart they are. Does that not send a signal faster than light? No, and the reason is in the randomness you just saw. When you measure your half, you get a random result, 0 or 1, fifty fifty. You do not get to choose it. So you cannot encode a message in it, because you have no control over what comes out. The person holding the other qubit also sees a random bit. The two random bits happen to match, but neither person can tell, from their side alone, that anything special occurred. Their qubit just looks like a coin flip, exactly as the marginal showed. The only way to discover the correlation is for the two of you to get together and compare your lists of results, and that comparison travels over an ordinary channel, a phone call, an email, something no faster than light. No usable information crosses the gap at the moment of measurement. Nothing you could ever signal with. The correlation is real and the communication is not, and holding those two facts together is the whole trick. The word instantly is also doing more work than it can carry. For two measurements far enough apart, which one happened first depends on who is watching, and there is a frame of reference in which the other one went first. Nothing about the correlation changes. That is another way of seeing that no signal is passing between them. ## "But that is just two sealed envelopes" A sharp objection turns up here, and it is worth taking seriously because answering it is what reveals the real content. You might say: this is nothing special. Flip a coin, put a card of the same colour, red or blue, into each of two envelopes, and mail them to two cities. Whoever opens theirs instantly knows the other's colour. Perfect correlation, no magic, no physics, and certainly no faster than light anything. Is an entangled pair not just a fancy version of that? For the simple measurement above, you are almost right, and that is the honest part most explanations skip. If all you ever do is measure both qubits the same way, the Bell state behaves exactly like two sealed envelopes. The correlation you see could be explained by the qubits having agreed on their answers at creation, like the cards chosen before mailing. Nothing you have seen so far rules that out. This is why I keep insisting entanglement is a correlation and not telepathy: at this level of looking, the envelope story fits. The envelopes break only when you start measuring the two qubits in different, cleverly chosen ways and compare the statistics across many runs. That is where the quantum correlations climb past anything sealed envelopes could ever produce, and it is exactly the experiment the next section describes. So hold the envelope picture as your baseline, because it is correct as far as it goes, and watch what it cannot reach. ## So what is actually surprising I promised honesty, so here it is. The genuinely strange part is not the speed. It is the strength of the correlation. You might think the qubits simply agreed on an answer in advance, like two people who synchronised their watches before leaving, so of course they match later, no magic needed. For decades that was the natural sceptical position: maybe each qubit secretly carries a hidden instruction set, decided at creation, and measurement just reads it out. It turns out you can test that idea, and it fails. There is a class of experiments, Bell tests, that measure entangled pairs in cleverly chosen ways and count how often the results agree. Suppose each qubit carries its own instruction set, fixed at creation, and answers from it alone, untouched by whatever happens to its partner far away. That assumption puts a hard ceiling on how correlated the two can be. Quantum mechanics predicts correlations above the ceiling, and the experiments, run many times by many groups over decades, see the quantum number, not the ceiling. So the watch synchronising story is dead. There is no local hidden instruction set, and the correlation is stronger than any advance agreement of that kind could produce. Be careful about what that settles, because this is where popular explanations reach for the mysticism again. The experiments rule out local instruction sets. They do not tell you what to put in their place, and physicists who agree perfectly on every number still disagree about that: some give up locality, some give up the idea that the answers existed before anyone looked, some reject the question's framing entirely. Those arguments are live, not settled, and this post is not going to resolve them. What is settled is the measurement, and the measurement is enough. A correlation that no advance agreement can account for, verified in laboratories for decades, is far more interesting than telepathy. ## It scales: three qubits, all agreeing Entanglement is not a two qubit party trick. You can entangle three, or thirty. Add one more CNOT to the recipe and you get a three qubit state where all three agree. ```python ghz = QuantumCircuit(3) ghz.h(0); ghz.cx(0, 1); ghz.cx(1, 2) print(np.round(Statevector(ghz).data, 3)) # weight only on |000> and |111> ``` Run it. The state is (|000⟩ + |111⟩) over √2, with zero amplitude on all six mixed outcomes. This one has a name too, the GHZ state, and measuring it gives 000 or 111, never anything in between. All three qubits agree, every time, the two qubit story extended. These larger entangled states are workhorses in error correction and in the protocols that need many qubits locked together, and the recipe stayed almost the same: a Hadamard to start the superposition, then CNOTs to spread the agreement down the line. ## There is more than one Bell state A small thing that tidied up my mental model: the pair you build is not the only entangled two qubit state. There is a whole continuum of them, and sitting inside that continuum is the set everybody actually uses, the four Bell states. They are mutually orthogonal, they are maximally entangled, and together they form a complete basis for two qubits. You reach the other three by tweaking the recipe. Slip a Z gate in before the CNOT, for instance, and you flip a sign. ```python phi_minus = QuantumCircuit(2) phi_minus.h(0); phi_minus.z(0); phi_minus.cx(0, 1) print(np.round(Statevector(phi_minus).data, 3)) # amplitude 0.707 on |00>, -0.707 on |11> ``` Run it. Now the state is (|00⟩ minus |11⟩) over √2, the same perfect correlation but with a relative phase between the two terms, |00⟩ and |11⟩, the kind of phase a later post is entirely about. The four differ in which outcomes correlate and in that internal sign, and because they are mutually orthogonal they can be told apart from one another with certainty, which is exactly why they show up everywhere from teleportation to error correction. The point for now is just that entanglement is a family, not a single trick, and small changes to two gates walk you around it. ## A deep limit hiding nearby: you cannot copy a qubit Worth knowing, because it explains why entanglement is used the way it is. You cannot copy an unknown quantum state. There is no operation that takes an arbitrary qubit and produces two identical copies of it. This is the no cloning rule, and it is not an engineering limitation that better hardware will fix, it is a theorem, a flat consequence of how quantum mechanics works. It follows from something [the gates post](https://tuguidragos.com/quantum-gates-are-just-matrices-you-can-run-by-hand/) already established: gates are matrices, and matrices are linear. A machine that copied any state you handed it would have to behave inconsistently on superpositions, and linearity simply forbids that. The intuition people usually reach for is that to copy a state you would first have to know it, and to know it you would have to measure it, and measuring collapses it, destroying the very thing you wanted to duplicate. That is not the proof, but it points in the right direction. Either way you are stuck. The original is all you get. This sounds like a pure restriction, and it is the reason quantum teleportation has its odd name. You cannot clone a qubit to send a copy elsewhere, so the only way to move a quantum state from here to there is to transfer it, consuming the original in the process. The state ends up at the destination and is gone from the source, a move and not a copy, forced by no cloning. And the resource that makes that move possible, the channel it travels through, is a shared entangled pair. The limit and the entanglement work together, which is why they belong in the same post. ## Where entanglement earns its keep This is not a curiosity off to one side. Entanglement is the resource behind the parts of quantum computing that have no classical shadow. Quantum teleportation uses it to move a state from one qubit to another. Superdense coding uses it to pack two classical bits into the transmission of one qubit. The algorithmic speedups lean on it heavily. When people say quantum is more than a fancy probabilistic computer, entanglement is a large part of what they mean, because it is correlation that no separate, independent description can reproduce. Each of those uses deserves its own post, and will get one. For now it is enough to know that the two line recipe above is the doorway to all of it. ## A quick test before you move on Close this and answer in your own words. If you measure one qubit of a Bell pair and get 1, what will the other give, and how sure are you? If your answer is not the other is 1, with certainty, reread the measurement section. Why can you not use an entangled pair to send a message faster than light? If the word random is not central to your answer, the no communication section did not land. And what does it actually mean to say a state is entangled, in terms of describing the two qubits? If you cannot say something like the parts have no separate description, go back to the section on the whole and the parts, because that is the definition the rest hangs on. ## Where I am learning it Free. 3Blue1Brown and the usual visual explainers are good for the geometry of it, though I will warn you that a lot of popular material leans straight into the spooky framing this post is trying to undo, so read it with that filter on. The cleanest mental model came from building the Bell state myself and poking at it, measuring it, taking its marginals, trying and failing to factor it into two separate qubits. As with everything else I write, the correlation I had only read about did not become real until I made two qubits agree on my own machine and watched the mixed outcomes stay stubbornly empty. ## The spooky was the marketing Entanglement was sold to me as the part of quantum I would never really understand, the mystical core, action at a distance, give up and accept it. It is a correlation you can build in two gates. The qubits agree perfectly, each one alone looks like noise, and you cannot use any of it to send a signal, because the agreement only shows up when you compare notes the slow way. What is left, once the marketing is gone, is stranger and better than the marketing was: a correlation stronger than any advance agreement could produce, confirmed in the lab again and again, and still argued over by people who agree completely on every number it returns. The spooky was a slogan. The thing underneath is real, buildable, and now yours to make whenever you want. ### Six defects in a verified n8n node for IBM Quantum URL: https://tuguidragos.com/six-defects-verified-n8n-node-ibm-quantum/ Last updated: 2026-09-13T18:42:29.000Z The error told me my circuit contained a gate my circuit did not contain. ``` Failed to execute program: 'Circuit 0: The instruction u on qubits (1,) is not supported by the target system. -- Transpile your circuits for the target before submitting a primitive query. reason_code: 1517 ``` I read it three times. There was no `u` anywhere in that circuit. I had built it deliberately out of the gates `ibm_kingston` reports as its own native set, which is the whole point of an ISA circuit, and the machine was refusing an instruction I had never written. That was the start of eight hours that turned up six defects in my IBM Quantum node for n8n, and three things I could only have learned by putting circuits on real hardware. The package they were sitting in was already verified by n8n. Every unit test passed. Statement coverage was 98.55 percent. `npm audit` was clean. None of that had anything useful to say about any of the six. What did have something to say was 121 seconds of QPU time across two IBM machines. This is what it found. ## The gate that lies about itself The circuit that produced that error contained an Identity gate. So I checked whether `id` was native on that backend. ``` basis_gates ibm_kingston: ["cz","id","rx","rz","rzz","sx","x"] ``` It is right there. IBM's own backend, reporting `id` among its native gates, on the same device that had just refused the job. That made it worse, not better. A user building an ISA circuit sees that list, includes `id` because the vendor says the chip runs it, and the job dies naming an instruction that appears nowhere in their program. The cause is in the language, not the hardware. `stdgates.inc`, the OpenQASM 3 standard library, defines identity as `gate id a { U(0, 0, 0) a; }`. IBM's parser expands it into the builtin `U`. The target refuses `U`. The name matches a native instruction, and the path never reaches it. A theory that sounds right is the most expensive kind of wrong, so I put it on hardware. Three control jobs, one variable at a time: | circuit | result | | --------------------------------------- | ---------------------------- | | reset + x + rz + cz + barrier + measure | completed, {"01":120,"00":8} | | rx(pi) alone | completed, {"1":123,"0":5} | | id + x + measure | failed, instruction u | Six seconds of quota to turn a theory into a fact. The fix keeps Identity in the palette and still validates its operands, so a wrong qubit index is still an error rather than a silent drop, but it emits nothing at all. Identity is the no op, so what comes out is mathematically identical and it runs. Same circuit that had failed: ``` QASM emitted: qubit[2] q; bit[2] c; x q[0]; c[0] = measure q[0]; c[1] = measure q[1]; status: completed counts: {"01": 249, "00": 7} ``` Reproduced on `ibm_kingston` and on `ibm_marrakesh`. If you take one thing from this post, take this. `basis_gates` tells you what the chip can execute. It does not tell you what the parser will hand the chip. A gate the vendor lists as native can still be unreachable, because of how the standard library of the language happens to define it, and nothing in your local test suite will ever tell you. ## A gate I built, tested and then deleted Look at that basis gate list again. `rzz` is in it too. So I implemented it properly: arity, rendering, palette entry, unit tests, build, redeploy into n8n. Then onto real hardware, at two different angles so a bad parameter could not be the explanation. ``` RZZ(pi): failed "Error parsing OpenQASM program" RZZ(pi/2): failed "Error parsing OpenQASM program" ``` Parse error, not target error. IBM's OpenQASM parser does not recognise the symbol at all. It never gets far enough to ask the hardware. At that point I had a suspicion and no proof, which is the same position I had been in twenty minutes earlier with `id`, so I did the same thing. Same circuit, `rzz` removed, nothing else touched. ![n8n Get Results output showing counts 00 at 480 and 01 at 32 over 512 shots on ibm_kingston](https://tuguidragos.com/content/images/2026/08/same-circuit-without-rzz--on-ibm_kingston-1.png) The control, read back through the node. Job `d9nl060qs0bc73e3ifvg`, the same ID tagged `qa-rzz-control` in the console at the top of this post. ``` status: completed counts: {"00":480,"01":32} -> 93.8% "00" ``` Which is exactly what the circuit predicts, since two Hadamards cancel. The program was fine. Only `rzz` broke the parser. The console screenshot at the top of this post holds all three of those jobs in one frame, consecutive rows on `ibm_kingston`: `qa-rzz-pi` failed, `qa-rzz-half` failed, and directly beneath them `qa-rzz-control` completed. So I deleted the implementation. All of it, after building it. Deleting working code you have just finished feels like waste. It was not waste. Shipping it would have recreated the precise trap the `id` fix had just closed: a gate sitting in the palette, listed as native by the backend, failing every job it ever touched. The only difference is that this time I would have put it there myself. The knowledge stayed, written into the README and the CHANGELOG. The code went. ## The third thing only hardware could tell me The first GHZ-40 run, forty qubits prepared so that they all read 0 or all read 1 together, failed on a restriction that is easy to trip and easy to miss: ``` Gate twirling does not support fractional gates. -- If the PEC, PEA, or gate twirling option is selected, then make sure to remove all fractional gates from the circuits. ``` The circuits Qiskit transpiled for a Heron processor in this campaign use parametrised `rx`, which IBM classifies as fractional. Which means anybody who transpiles with fractional gates enabled and ticks Gate Twirling because more error suppression sounds better, loses the job. Dynamical Decoupling and Measurement Twirling carry no such restriction. That warning now lives in the parameter description itself, where a user reads it inside n8n at the moment it matters, rather than in a document nobody opens. Not one of those three things was reachable from my laptop: a defect in my own code, a gate I deleted after building it, and a restriction inside IBM's stack. That is the honest case for spending real quota on your own tooling. The next one is the reverse. Hardware confirmed it. Hardware could never have caused it. ## A measure that wrote outside its own register This defect exists only because the node can be called without the editor, by an AI Agent for one. A `measure` gate with no value in the Classical Bit field passed validation and produced a program IBM rejects. Two code paths used different fallbacks for the same missing value. Validation checked `clbit ?? 0`. Rendering used `clbit ?? targets[0]`. Reproduced against the compiled `dist` before the fix: ``` validateGateInput -> VALID (no error) QASM emitted: qubit[3] q; bit[1] c; c[2] = measure q[2]; <- writes to bit 2 of a one bit register ``` That field can only go missing when the gate object is built outside the n8n editor. By a model calling the node as a tool, by an imported workflow, or through the public n8n API. Inside the editor it always carries a value, so no amount of clicking would ever have surfaced it. The fix normalises to 0 in `mapGate` and uses the same fallback in the renderer, so the two cannot drift apart again. Confirmed on hardware: the test emitted `c[0] = measure q[2];` and the job completed. The general form is worth stating plainly. If your node sets `usableAsTool`, every optional field becomes a field a model can omit. Defaults that live in the interface are not defaults. ## Physics does not care what you believe Every job ran through a local n8n 2.32.7 instance with the node installed exactly the way n8n installs a community package: `npm pack`, extract the real tarball into `.n8n/nodes/node_modules`, `npm install --legacy-peer-deps`. Not `npm link`. If the test does not use the install path real users get, it is not a test of the thing they receive. The reason quantum circuits make an unusually good test suite is that the answer is known in advance and the machine has no interest in agreeing with you. If any link in the chain is wrong, from building the PUB, the bundle of circuit, parameters and shots a job carries, to decoding hex samples back into counts, the numbers simply do not arrive. | test | predicted | measured | | ---------------------------------------------- | ---------- | ---------------------- | | Estimator ⟨Z⟩ on \|1⟩ | \-1 | \-1.0024 | | Estimator, Pauli array \["ZI","IZ","ZZ"\] | +1, -1, -1 | +1.003, -1.014, -1.025 | | Estimator, coefficient map {"ZI":1.0,"IZ":0.5} | +0.5 | +0.472 | | Parametrised rx(theta) at θ = π | \|1⟩ | {"1":125,"0":3} | The parameter sweep is the strongest single piece of evidence in the whole campaign. One submission, five bindings, and a cosine came back out of a superconducting chip: ``` theta predicted cos(t) measured delta 0.0000 1.0000 1.0013 0.0013 0.7854 0.7071 0.7262 0.0190 1.5708 0.0000 0.0135 0.0135 2.3562 -0.7071 -0.6946 0.0125 3.1416 -1.0000 -1.0058 0.0058 ``` Maximum deviation 0.019\. You cannot fake that curve with a broken parser. One detail in those two tables deserves a word, because a physicist will spot it immediately. Several of the measured [expectation values](https://tuguidragos.com/measurement-is-not-magic-the-born-rule-by-hand/) sit outside the range a Pauli operator can actually take, -1.0058 and +1.003 among them, and no real ⟨Z⟩ ever leaves the interval from -1 to 1\. That is not a parsing fault and it is not the chip misbehaving. The Estimator applies error mitigation by default, and a mitigated estimate is corrected on average rather than clamped to the physical range, so individual points overshoot in both directions. The overshoot is small, it is symmetric, and it is what a working mitigation stack looks like. A parser inventing numbers would not land this close on both sides of the line. Two parsing details came out of the same campaign, and both were silent failures waiting to happen. **`num_bits` was the last open question, and it got answered on real data.** The samples arrive as `0x1` and `0x0`. If the API had not sent `num_bits`, my width inference would have returned 1, and the results would have read `{"1":250,"0":6}` instead of `{"01":250,"00":6}`. The second bit would have disappeared without a trace and every count would still have summed correctly. It is present, at 2 and at 64. **BigInt earns its place at 64 bits.** The wide register test used 2⁶³ + 1\. As an IEEE double that rounds to 2⁶³ and the low bit vanishes, so `parseInt` would have produced `1000...0000`. The parser produced `1000...0001`. I also built a Bell state out of native gates with no transpiler anywhere, using the two identities that do most of the work: H equals rz(π/2) then rx(π/2) then rz(π/2), and CNOT from control to target equals H on the target, cz, H on the target again. Thirteen gate entries, on `ibm_marrakesh`, 2048 shots: | outcome | count | share | | ------- | ----- | ----- | | 00 | 1046 | 51.1% | | 11 | 946 | 46.2% | | 01 | 25 | 1.2% | | 10 | 31 | 1.5% | 97.3 percent correlated, 2.7 percent readout noise, 3 seconds of quota. If you want to see the matrices behind gates like H and CNOT, I worked them by hand in [an earlier post](https://tuguidragos.com/quantum-gates-are-just-matrices-you-can-run-by-hand/). ## Eighty qubits, depth 241, and samples too wide for a double My README had been recommending Qiskit for transpilation, and that recipe had never been verified end to end. So I ran it. Qiskit 2.2.3 in an isolated environment, and the real topology of `ibm_kingston` read through the node itself with Backend then Get Configuration, which returned 352 coupled pairs and the native gate list. Nothing assumed. | circuit | logical depth | transpiled depth | CZ gates | shots | result | cost | | | ------- | ------------- | ---------------- | -------- | ------ | ------------------------------- | ---- | --- | | GHZ-12 | 13 | 37 | 11 | 8,192 | 37.7% \|0¹²⟩, 34.3% | 1¹²⟩ | 4 s | | GHZ-40 | 41 | 121 | 39 | 15,000 | ranks 1 and 2 of 4,853 outcomes | 6 s | | | GHZ-80 | 81 | 241 | 79 | 25,000 | ranks 1 and 2 of 23,294 | 9 s | | | GHZ-80 | 81 | 241 | 79 | 45,000 | ranks 1 and 2 of 41,172 | 14 s | | The heaviest run, in full: ``` 80 physical qubits, depth 241, 79 CZ gates, 45,000 shots |0^80> : 0.48% rank #1 |1^80> : 0.40% rank #2 outcome space: 2^80 = 1.2 x 10^24 possible strings every string exactly 80 bits : PASS sum of counts = shots (45000) : PASS payload: 1041 KB downloaded and parsed inside the 30 s timeout ``` Out of 1.2 x 10^24 possible bitstrings, the two GHZ extremes come first and second. On a noisy processor, at depth 241, through 79 entangling gates. The outcome space is far wider than a double can hold exactly. ![n8n executions list showing 75 production executions with a zero percent failure rate and named QA workflows](https://tuguidragos.com/content/images/2026/08/executions.png) The n8n side of the campaign. Each workflow is one test, named for what it checks, including the 45,000 shot GHZ-80 run at the bottom of the visible list. All 24 operations went against live infrastructure, not only the interesting ones. Both polling triggers fired with the failure reason attached. Seven error paths. Sessions created, queried, closed, including one batch that genuinely carried two jobs rather than being opened and closed empty. Private jobs, where IBM redacts the submitted parameters and leaves the tags visible, which confirms the redaction is selective exactly as documented. And one test I did not plan. Partway through, `ibm_marrakesh` went into maintenance, so I checked on the spot whether Get Least Busy would exclude it: ``` marrakesh state: message "maintenance" Get Least Busy candidates: kingston (queue 2), fez (queue 5) marrakesh excluded: PASS ``` `ibm_fez` is the only backend I touched without running anything on it. It was queried through the API and never received a job. ## The filter that returned everything and never said so The remaining defects are a different species. They were not failures at all. They were wrong answers, delivered confidently, with a 200 on them. Filtering jobs by tag did not filter. It did not error. It returned everything. n8n serialises an array of query parameters as `tags[]=a&tags[]=b`. IBM's API recognises repeated keys, `tags=a&tags=b`, and silently ignores what it does not recognise. No warning. Status 200, full list, every time. ``` BEFORE arrayFormat: filter 'qa-audit' -> 20 jobs, first ones tagged "qa-ghz80-max" filter with a nonexistent tag -> 20 jobs AFTER arrayFormat: 'repeat': filter 'qa-audit' -> 1 job, tags ["qa-audit","retagged"] filter 'qa-audit, retagged' -> 1 job (real AND) filter with a nonexistent tag -> 0 jobs ``` The unit tests passed the whole time, because they asserted the shape of the request my code built. Nobody had ever asked the server what it did with it. This is the cleanest example I have of the distance between "the tests pass" and "it works", and it only closed because I checked a filter against the live API instead of against my own expectations. Three smaller ones came from the same family of misplaced trust. `parseResults` guarded with `?? []`, which catches only `null` and `undefined`, so a `results` field that was not an array reached `.map` and threw a bare `TypeError` instead of a readable n8n error. It was found by writing a test that failed, not by reading the code. The numeric fields trusted the interface, because `minValue` in n8n is a visual hint and an expression can deliver a string, a float or a negative straight into the request, so `shots` went into the PUB unclamped and wasted a submission. And Submit checked less than Import did: the literal string `this is not qasm` was accepted, received a job ID, queued, and failed on IBM's servers. That cost 2 seconds of quota to learn. ## Coverage measures what ran, not what was right The suite went from 140 tests to 185\. Statements from 98.55 to 99.8 percent, branches from 89.81 to 97.83, lines to 100\. Thresholds were raised from 85/85/80 to a set that sits directly under the measured figures, so a regression now trips CI instead of passing quietly. Ten branches remain uncovered and each is commented where it lives, because each is unreachable by construction. Here is the part that changed how I read a coverage report. **The `measure` bug was on a line reported as 100 percent covered.** The existing test passed `clbit` explicitly, so the line executed every run and the fallback branch never did. Same story with `parseResults`. Coverage tells you which lines ran. It has no opinion whatsoever about whether they were correct. ## A failed circuit is not free Worth knowing if you are on the Open plan and its 600 seconds per rolling 28 day window. IBM defines usage as the time a QPU is locked for your workload, and its documentation is explicit about failures: a job that fails from a system error reports zero usage, but a job that fails through user error, or is cancelled by the user, is charged for whatever was consumed up to that point, **including the overhead of preparing the QPU to run it**. The same page puts that per sub-job overhead at approximately 2 seconds. That is exactly what I measured. Every one of my five deliberate negative tests, the invalid QASM, the `id` gate, both `rzz` attempts and GHZ-40 with Gate Twirling, cost precisely 2 seconds. Ten seconds of that 600 spent on circuits designed to fail. That is a price worth knowing before you plan a campaign. The same page defines batch usage as the cumulative QPU lock time of all jobs in the batch, which means the Batch rows in the console are aggregates of jobs already listed individually. Adding them to a per job total double counts. ## What 0.3.3 ships, and what it took to publish it ![GitHub repository page for the n8n-nodes-ibm-quantum community node at release 0.3.3](https://tuguidragos.com/content/images/2026/08/n8n-nodes-ibm-quantum.png) The repository at commit 3516e51\. Three nodes, one credential, twenty four operations, zero runtime dependencies. [n8n-nodes-ibm-quantum](https://www.npmjs.com/package/n8n-nodes-ibm-quantum) is three nodes and one credential, MIT, zero runtime dependencies. The [source is on GitHub](https://github.com/TuguiDragos/n8n-nodes-ibm-quantum). Circuits travel as OpenQASM 3 strings and the IBM Cloud API key is exchanged for a short lived IAM bearer token at request time. Twenty four operations across Backend, Circuit, Job, Session and Account. Four things are new, each verified against the live API rather than a mock: - **A Program filter on Job List.** Sampler or Estimator. Checked live: 20 sampler, 12 estimator, empty returns both. - **Multiple Tags filters** on Job List and both triggers, up to the eight the API accepts, with AND semantics. A tag that does not exist now returns zero jobs, which is what the old code could not do. - **An `unparsedSamples` field** that appears only when the parser cannot read part of a register, so a gap between `counts` and `shots` is visible instead of silent. Absent on healthy data, confirmed present on the 80 qubit payload. - **A documented recipe for ISA circuits without a transpiler**, the one that produced the Bell state above. Then there is the part that has nothing to do with quantum computing. I ran the official `@n8n/scan-community-package` against my own published package, the one already carrying the verified badge. ``` BEFORE (git HEAD = published 0.2.3): passed=false errors=10 AFTER (repaired working tree): passed=true errors=0 ``` Ten errors, on a package that was verified and installed by other people. The scanner does not lint your working tree. It checks the provenance attestation, downloads the source from the commit that attestation points to, and lints it with `eslint-plugin-n8n-nodes-base` in addition to the community nodes plugin. My local config loaded only the second, and did not lint `package.json` at all. One of the ten is worth showing in full, because it is the kind of rule you cannot guess at. Both trigger nodes failed the display name check, which accepts exactly one trailing parenthetical, and mine was not the one it accepts. ```js const displayValue = displayName.value.replace(/\s*\(Beta\)$/, ""); if (!displayValue.endsWith("Trigger")) { /* report */ } ``` `(Beta)` is stripped before the check. `(Unofficial)` is not. If you maintain an n8n community node, run `npm run scan` against your published package, not your working tree. The rest of the release, briefly: - **Node 20 is out.** It reached end of life on 30 April 2026, so `engines.node` moved to `>=22` and CI runs on 22 and 24\. To be accurate: this is a policy decision, not a technical necessity. The n8n 1.x line is still actively published and still permits Node 20.19\. I dropped it because shipping against an unpatched runtime is a bad default. - **Publishing moved to npm trusted publishing over OIDC.** The token had expired, and instead of regenerating it I removed it. The workflow now mints a short lived, workflow scoped credential, provenance attestations generate automatically, and there is nothing left in the repository to rotate. One trap on the way: trusted publishing needs npm 11.5.1 or newer, and Node 22 ships npm 10.9.8, so the publish job moved to Node 24. - **The lint config now matches the scanner**, including `package.json`, which needs the TypeScript parser because those rules walk a TSESTree AST. Roughly a dozen rules had never run locally. I verified it by reintroducing each class of error and confirming the lint failed. - **Dependencies aligned to the versions that actually matter:** `@n8n/eslint-plugin-community-nodes` 0.27.0, the version the scanner itself pins, and `n8n-workflow` 2.32.1, the exact version inside n8n 2.32.7\. All four `npm audit` findings cleared. TypeScript 7 stays out, because it drops `moduleResolution=node10` and typescript-eslint does not load against it. - **CI caught something I could not have caught locally.** The lockfile, generated on macOS with npm 10.9.8, omitted optional binaries for other platforms. npm 11 on Node 24 is stricter and refuses `npm ci`. Regenerated with npm 11, 463 entries added, verified npm 10 still accepts it. Three commits, 26 files, 1922 insertions, 2229 deletions. The third one exists because on rereading I noticed the CHANGELOG claimed the new thresholds meant a regression would trip the gate, and it would not have, since CI ran `npm test`, which does not check thresholds. I made the sentence true rather than deleting it. ``` main 3516e51 npm 0.3.3 latest, published 2026-08-02T15:13:46Z gitHead ae4a461 (matches the git tag exactly) engines >=22 runtime dependencies: zero provenance npm attestation + SLSA, generated automatically through OIDC package 38 files, 170.7 KB unpacked ``` The package downloaded from npm, not my local build, also passed all 22 checks of the n8n loader contract. ## What I am not claiming **That the node has no bugs.** This session is the counterexample. It was verified, tested and covered, and it had six. It has now been interrogated seriously. That is a different sentence from "it is correct". **That coverage is meaningless.** Two defects sat on fully covered lines, which makes coverage a floor rather than a verdict. It is still worth having. **That `sx` is broken.** I did not test it individually on hardware. What I know is that the palette has no way to emit it without going through the builtin `U`, so it is not offered. That is not the same as "tested and it fails". **That every gate classification is empirical.** The gates marked "transpile first" in the README are classified from the `basis_gates` the backend reports, which is a solid source, but only `x`, `rx`, `rz`, `cz`, `reset`, `barrier` and `measure` have been confirmed by jobs that completed. **That Node 20 could not run this.** It could. I dropped it on policy. ## Questions people ask ### Why does my IBM Quantum job fail with "the instruction u is not supported" when my circuit has no u gate? Almost certainly because it contains an identity gate. `stdgates.inc` defines `id` as `U(0, 0, 0)`, IBM's parser expands it into the builtin `U`, and the target refuses `U`. The backend lists `id` in its own `basis_gates`, which is what makes this so confusing. Remove the identity gates. They are no ops, so the circuit is unchanged. ### What does reason\_code 1517 mean? The circuit was not transpiled to the target's native gate set. Either transpile it with Qiskit against the real backend, or build it directly from the native gates the device reports. ### Why does Gate Twirling fail my transpiled circuit? Because Qiskit, with fractional gates enabled, transpiles for Heron processors using parametrised `rx`, which IBM classifies as a fractional gate, and gate twirling does not support fractional gates. Turn it off. Dynamical Decoupling and Measurement Twirling have no such restriction. ### Can I use rzz on IBM hardware through OpenQASM 3? Not through the REST API as of August 2026\. The backend lists `rzz` in `basis_gates`, but submitting it produces `Error parsing OpenQASM program` before it ever reaches the target. Verified twice on `ibm_kingston`, with a control circuit confirming the rest of the program was valid. ### Do failed quantum jobs use up my monthly quota? Yes, when the failure is your fault. IBM reports zero usage for a system error, but a user error or a manual cancellation is charged for whatever was consumed, including the roughly 2 second QPU preparation overhead. Five deliberately broken circuits cost me 10 seconds. ### Can you build an ISA circuit without Qiskit? For small circuits, yes. Through this node's palette the usable native set on a Heron processor is `x`, `rx`, `rz`, `cz`, plus measure, reset and barrier. H equals rz(π/2), rx(π/2), rz(π/2), and CNOT equals H on the target, cz, H on the target. A Bell state built that way returned 97.3 percent correlation on `ibm_marrakesh`. ### Why did a verified n8n community node fail the official scan? Because the scanner downloads the source from the commit the provenance attests to and lints it with `eslint-plugin-n8n-nodes-base`, which many local configs do not load, and it lints `package.json`, which many do not cover. Run `npm run scan` against the published package. ## The run in numbers Run window: 2 August 2026, 13:38:45 to 14:22:19 UTC. Forty three minutes. | | | | ------------------------------------------------- | --------------- | | jobs submitted | 28 | | completed | 22 | | failed, of which 5 were deliberate negative tests | 6 | | QPU time consumed | 121 s | | jobs on ibm\_kingston | 25 | | jobs on ibm\_marrakesh | 3 | | jobs on ibm\_fez | 0, queried only | | monthly Open plan allowance | 600 s | The per job usage column sums to exactly 121, and that matches the change in reported instance usage across the campaign. Two independent sources, same number. The jobs behind the claims in this post, so you can match them against the console: | tag | job ID | backend | what it shows | | --------------- | -------------------- | -------------- | --------------------------------------------- | | qa-idfix | d9nkqacsfqic73ar5o0g | ibm\_kingston | the identity circuit completing after the fix | | qa-rzz-pi | d9nkvm4sfqic73ar5t5g | ibm\_kingston | rzz at π, parse error | | qa-rzz-half | d9nkvmcsfqic73ar5t60 | ibm\_kingston | rzz at π/2, parse error | | qa-rzz-control | d9nl060qs0bc73e3ifvg | ibm\_kingston | same circuit without rzz, completed | | qa-bell-native | d9nktfoqs0bc73e3id5g | ibm\_marrakesh | Bell state from native gates only | | qa-est-map | d9nkmbs60llc73ca7e90 | ibm\_kingston | Estimator with a coefficient map | | qa-param | d9nkmkc60llc73ca7epg | ibm\_kingston | parametrised rx at θ = π | | qa-bigpayload | d9nkn7oqs0bc73e3i53g | ibm\_kingston | 64 bit register, BigInt decoding | | qa-ghz12-qiskit | d9nl2tk60llc73ca7v1g | ibm\_kingston | first Qiskit transpiled run | | qa-ghz40 | d9nl3rssfqic73ar622g | ibm\_kingston | Gate Twirling on fractional gates, failed | | qa-ghz40 | d9nl450qs0bc73e3ikm0 | ibm\_kingston | same circuit, twirling off, completed | | qa-ghz80-max | d9nl56ssfqic73ar63fg | ibm\_kingston | 80 qubits, depth 241, 45,000 shots | The environment, if you want to reproduce any of it: n8n 2.32.7 on Node 22.23.1, with an isolated data folder outside `~/.n8n` and telemetry disabled. Qiskit 2.2.3 with NumPy 2.0.2 in a separate virtual environment, used only for transpilation. IBM Quantum Platform in us-east, `IBM-API-Version` 2026-04-15, Open plan, Heron r2 processors at 156 qubits, which report a max\_shots of 100,000 in their configuration, a legacy field IBM says it no longer enforces. > **A note on one number.** The published CHANGELOG for 0.3.3 records 118 seconds of QPU time. That figure was written before the campaign finished. The number in this post is 121, and it is the one supported by both the per job usage column and the reported instance usage. I have left the CHANGELOG as published rather than editing history, so this note is here to explain the difference rather than hide it. ## Where to find it The node is on npm as [n8n-nodes-ibm-quantum](https://www.npmjs.com/package/n8n-nodes-ibm-quantum). It installs from the [community nodes screen](https://docs.n8n.io/integrations/community-nodes/installation-and-management) on self hosted n8n, and directly from the canvas on n8n Cloud because it is verified. Source, CHANGELOG and the full gate table are in [the repository](https://github.com/TuguiDragos/n8n-nodes-ibm-quantum). It is unofficial and not affiliated with IBM. Earlier chapters, if you want them: [the launch and what the three nodes do](https://tuguidragos.com/ibm-quantum-node-for-n8n/), and [the first end to end Bell state on real hardware](https://tuguidragos.com/quantum-circuits-ibm-hardware-n8n/), which covers the transpilation step most first attempts miss. IBM's documentation on [workload usage](https://quantum.cloud.ibm.com/docs/en/guides/estimate-job-run-time) is the source for the quota accounting above, and worth reading before you spend a monthly allowance. What I keep turning over is that none of the six defects came from not knowing enough quantum computing. Every one came from trusting a layer. That green tests meant working behaviour. That a covered line meant a correct line. That a gate the vendor lists as native is a gate you can use. The hardware had no opinion about any of it. That is the whole value of running the thing for real. ### Quantum gates are just matrices you can run by hand URL: https://tuguidragos.com/quantum-gates-are-just-matrices-you-can-run-by-hand/ Last updated: 2026-09-13T18:42:26.000Z The word gate tripped me up for longer than I want to admit. It sounds like hardware. A physical component, a little door the qubit passes through, something happening at the level of atoms and lasers that I would need a physics degree to follow. So I treated quantum gates as magic for a while, things that did mysterious quantum stuff by means I was not equipped to question. Then my math notebook and the [Python post](https://tuguidragos.com/the-python-you-actually-need-for-quantum-computing/) collided in my head and the whole thing collapsed into something almost embarrassingly simple. [A qubit is a vector](https://tuguidragos.com/a-qubit-is-not-a-coin-under-a-cup/). A gate is a matrix. Applying a gate is multiplying the matrix by the vector. That is the entire mechanism. Not a metaphor, not an approximation. Literally that. Here are the gates that matter, what each one does, and the two laws they all obey, all of it runnable by hand. ## Apply a gate, by multiplying Start with the simplest possible case so the claim is concrete. The X gate is the quantum NOT. Here it is as a matrix, applied to the state |0⟩, which is the vector \[1, 0\]: ```python import numpy as np X = np.array([[0, 1], [1, 0]]) ket0 = np.array([1, 0]) print(X @ ket0) # [0 1] -> |1> ``` Run it. The X gate turned |0⟩ into |1⟩, a bit flip, and the act of applying it was a single matrix multiply, the @ from the Python post. That is what a gate does, every time, no exception. You take the matrix, you multiply it by the state vector, and the result is the new state. Everything below is just a catalogue of which matrices are worth knowing and what each one does to the vector. Once you have this picture, gate stops being a mysterious noun and becomes a thing you can compute on paper. ## The single qubit gates worth knowing There are a handful you will see constantly. Here is each one as a matrix and, more usefully, as a thing it does. **X, the NOT.** Swaps |0⟩ and |1⟩. The quantum bit flip, and the most familiar one because it has a classical twin. **Z, the phase flip.** Leaves |0⟩ completely alone and flips the sign of |1⟩. ```python Z = np.array([[1, 0], [0, -1]]) ket1 = np.array([0, 1]) print(Z @ ket1) # [0 -1] -> -|1> ``` Run it. |1⟩ became −|1⟩. Nothing about the probability changed, since the modulus is the same, but the sign did, and that sign is a phase. Hold that thought. It is the seed of an entire later post. **H, the Hadamard.** The workhorse of the whole field. It turns the definite states into superpositions and back. H on |0⟩ gives the equal superposition |+⟩, and because it is its own inverse, H on |+⟩ gives you |0⟩ right back. Almost every circuit you will ever read opens with a Hadamard somewhere, because it is how you get a qubit off the fence and into superposition in the first place. ```python s = 1/np.sqrt(2) H = s * np.array([[1, 1], [1, -1]]) print(np.round(H @ ket0, 3)) # [0.707 0.707] -> |+> ``` Run it. The same matrix multiply as the X gate, just a different matrix, and out comes the superposition. There is nothing special about how you apply it. Every gate is the same act. **Y, the third Pauli gate, after X and Z.** Effectively X and Z at once, with a factor of i mixed in, its matrix being \[\[0, −i\], \[i, 0\]\]. You will meet it constantly as a name, less often as something you apply by hand early on, so file it next to X and Z and move on. **S and T, the phase gates.** These rotate the phase of the |1⟩ part without touching its probability. S adds a quarter turn, T adds an eighth turn. Remember from the [complex numbers post](https://tuguidragos.com/complex-numbers-are-the-gateway-to-quantum/) that multiplying by i is a quarter turn on the circle. That is exactly what S does to the |1⟩ amplitude. ```python S = np.array([[1, 0], [0, 1j]]) print(S @ ket1) # prints [0.+0.j 0.+1.j], which is [0 i] -> i|1>, a quarter turn of phase ``` Run it. S sent |1⟩ to i times |1⟩, a ninety degree phase rotation, the quarter turn you already understand. And T is the half of S, in the sense that applying T twice equals S, which is a nice thing to verify yourself in a moment. ## Two laws every gate obeys, and why they matter Not every matrix is a legal gate. The legal ones all satisfy two conditions, and both conditions mean something physical. **Every gate is unitary.** Formally, the dagger of the matrix times the matrix equals the identity, U† U = I, using the conjugate transpose from the Python post. What it means in plain terms is that a gate preserves length. A valid quantum state is a vector of length one, because [its squared amplitudes are probabilities](https://tuguidragos.com/measurement-is-not-magic-the-born-rule-by-hand/) and probabilities sum to one. A gate has to keep that true. It can rotate the state, point it anywhere, but it can never stretch or shrink it, because a state of the wrong length would have probabilities that do not add up to one, which is nonsense. Unitary is just the mathematical way of saying this gate respects the rules of probability. And you can check it, the same way you check anything, with an assertion. ```python I = np.eye(2) H = (1/np.sqrt(2)) * np.array([[1, 1], [1, -1]]) assert np.allclose(H.conj().T @ H, I), "H is not unitary" print("H is unitary") ``` Run it. It passes in silence, the way a true assertion does. **Every gate is reversible.** This one is quietly profound. Because gates are unitary, every single one has an inverse, which means every gate can be undone. There is always a gate that takes you exactly back. Some gates are even their own inverse, Hadamard most famously: apply it twice and you are precisely where you started. ```python assert np.allclose(H @ H, I) # H undoes itself print("H applied twice is the identity") ``` Run it. This is a deep break from classical computing. An ordinary AND gate takes two bits in and gives one bit out, and from that one output you cannot recover the two inputs. Information is destroyed. Classical logic throws things away constantly. Quantum gates never can. Every operation is a reversible rotation, nothing is lost, and that single fact shapes an enormous amount of how quantum computing actually works. It is also worth a verification of its own. Remember the claim that two T gates make an S? ```python T = np.array([[1, 0], [0, np.exp(1j*np.pi/4)]]) assert np.allclose(T @ T, S) # T is the 'square root' of S print("T squared equals S") ``` Run it. T applied twice is exactly S, the eighth turn done twice making a quarter turn, which is just addition of angles dressed up as matrix multiplication. ## Stacking gates is just multiplying matrices Here is the step that turns single gates into a whole circuit, and it is short. A circuit is a sequence of gates applied one after another. Applying gate A and then gate B to a state is just B times the result of A times the state, which by the way matrix multiplication works equals the single matrix B times A, applied once. So an entire circuit collapses into one matrix, the product of all its gates, and running the circuit is multiplying that one matrix against your starting state. Two things fall out of this, both worth holding. The first is order. You multiply right to left, in the reverse of the order you apply the gates, because the gate you apply first sits closest to the state. And order matters, because matrices do not generally commute. A Hadamard then a Z is not the same as a Z then a Hadamard, and unlike some pairs the difference here is one you can actually measure. ```python plus = H @ ket0 # start from |+> print(np.allclose(H @ Z, Z @ H)) # False, the matrices differ print(np.round(np.abs(Z @ H @ plus)**2, 3)) # [1. 0.] -> H first, then Z: always 0 print(np.round(np.abs(H @ Z @ plus)**2, 3)) # [0. 1.] -> Z first, then H: always 1 ``` Run it. The matrices disagree, and so do the outcomes. Feed the same |+⟩ into both orders and one of them gives you 0 every single time while the other gives you 1 every single time. That is as stark as a difference in ordering gets, and it is the mathematical version of saying the sequence of operations changes the result. The second thing that falls out is the prettiest small fact in the early going. A Hadamard, then a Z, then a Hadamard, equals an X. Three gates, multiplied together, collapse to a single different gate. It is worth knowing why I did not reach for X and Z here, because that pair looks like the obvious choice and it is a trap. Multiply them out and you find XZ equals minus ZX. The two orders differ by nothing but an overall minus sign, and an overall factor of that kind never shows up in any measurement. The matrices are different and the physics is identical. Whenever you want to show that ordering matters, check that the two products differ by more than a single overall factor. ```python print(np.allclose(H @ Z @ H, X)) # True ``` Run it. H Z H is X, exactly. A phase flip wrapped in two Hadamards becomes a bit flip. This is not a coincidence, it is the rotation picture coming next, but even before that picture it is a concrete, checkable demonstration that gates compose by multiplication and that the composition can be something genuinely new. A whole circuit is one matrix you could, in principle, work out by hand. That is the entire idea of a circuit, hiding inside one line of NumPy. ## Gates are rotations Here is the picture that ties it all together. A single qubit's state can be drawn as a point on the surface of a sphere, called the Bloch sphere. |0⟩ at the north pole, |1⟩ at the south, the equal superpositions around the equator. You do not need the full machinery of it to take the one useful idea away, which is this: every single qubit gate is a rotation of that point. X, Y, and Z are half turns about three different axes. H is a particular rotation that swaps poles for equator. And there is a whole continuous family, written RX, RY, and RZ, that rotate the state by any angle you like about each axis. ```python def RX(theta): c, s = np.cos(theta/2), np.sin(theta/2) return np.array([[c, -1j*s], [-1j*s, c]]) print(np.allclose(RX(np.pi), -1j*X)) # True ``` Run it. A rotation of half a turn about the X axis is the X gate itself, give or take an overall phase factor. This is why the complex numbers post mattered so much. Multiplication by complex numbers is rotation, gates are rotations, and the two facts are the same fact seen from two angles. Once gates became rotations in my head, the discrete named gates and the continuous angle gates stopped being two separate topics and became one. Fixed gates are just rotations by special, useful angles. ## Two qubit gates, and the one that matters most Everything so far acts on a single qubit. The interesting things happen when gates act on two at once, and the one to know is CNOT, controlled NOT. It is a four by four matrix, because two qubits live in a four dimensional space, and what it does is conditional. It flips the second qubit, the target, but only if the first qubit, the control, is 1\. If the control is 0, it does nothing. ```python CNOT = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]) print(CNOT @ np.array([0, 0, 1, 0])) # slots are |00>, |01>, |10>, |11>, so this is |10> -> |11> ``` Run it. The state |10⟩, control 1 and target 0, came out as |11⟩, target flipped. CNOT is the gate that lets qubits affect each other, and it is the doorway to entanglement, which deserves and will get its own post. For now, just add it to the catalogue. A bigger matrix, doing a conditional flip, multiplied against a bigger vector. Same mechanism as everything else. ## In Qiskit, versus by hand In practice you will not multiply these matrices yourself. You will write qc.h(0) and qc.cx(0, 1), and Qiskit assembles the circuit for you, exactly as the Python post described, methods called on a circuit object. But the point of doing it by hand even once is that you can, and that you can check the library against your own arithmetic when something looks off. Qiskit will even hand you a circuit's matrix if you ask for it, through an Operator, so you can compare it to the one you built. One honest warning, the same one from the Python post. Qiskit numbers its qubits in the reverse of the textbook convention, qubit 0 on the right, little endian. So a CNOT matrix you pull out of Qiskit may not look identical to the one above, not because either is wrong, but because the qubit order is flipped. Knowing that the ordering convention exists saves you the exact evening I would otherwise have lost to it. ## A few gates are all you need One more fact worth carrying, because it surprised me and it changes how you see the whole catalogue. You do not need a separate exotic gate for every operation you might want. A small handful of gates is enough to build, to any precision you like, any circuit at all. A common universal set is the Hadamard, the T, and the CNOT. With just those three, composed in the right order and number, you can approximate any quantum computation there is. If that rings a bell, it should. Classical computing has the same property: every logic circuit you have ever used can be built from NAND gates alone. Universality is not a quantum quirk, it is a deep and reassuring fact about computation in general. A tiny, fixed alphabet, combined cleverly, expresses everything. It means hardware builders can pour their effort into making a few gates extremely well rather than chasing an infinite zoo, and it means that when you learn this short list, you are not learning a sample of the gates. You are learning, in a real sense, all of them, because the rest are just these few stacked up. The catalogue is not endless. It only looked that way from the outside. ## A quick test before you move on Close this and answer in your own words. What does applying a gate to a state actually do, mechanically? If your answer is not multiplies a matrix by a vector, the whole post did not land, so go back to the top. Why must every gate be unitary, in terms of probability? And what does unitary buy you that classical logic does not have? If reversible and nothing is destroyed are not in your answer, reread the two laws. And what do the S and T gates do to a state, and how does that connect to multiplying by i? If you cannot tie a phase gate to a quarter turn, that is your gap, and it is the one that pays off most later. ## Where I am learning it Free and, for this, visual is everything. 3Blue1Brown's linear algebra series is the single best thing I have watched for seeing a matrix as a transformation rather than a grid of numbers, which is the exact shift that makes gates click. MIT's 18.06 goes deeper if you want the full foundation. And my own notebook of gates as NumPy arrays, the one from the Python post, is where I actually check all of this, because a gate I have only read about is not a gate I trust until I have multiplied it against a state myself and watched the vector move. ## The magic was just a matrix Gate was the word that kept quantum feeling like a closed door. It sounded like hardware, like physics I had no claim to. It turned out to be a matrix, and applying it turned out to be the matrix multiply I already knew from the Python post, and the whole exotic vocabulary resolved into linear algebra I could do on paper. The hardware really is exotic. Building a physical qubit and holding it steady is genuinely the work of brilliant people with cryogenic fridges. But the gates, the part I was actually afraid of, the part I have to reason about to write a circuit, are not exotic at all. They are matrices. And I already know how to multiply a matrix by a vector. So do you. ### Active recall for hard technical subjects URL: https://tuguidragos.com/active-recall-for-hard-technical-subjects/ Last updated: 2026-10-03T13:34:21.000Z There is a specific feeling I have learned to distrust. It is the feeling, at the end of a study session, that I have got it. The page made sense. Every line followed the last. I nodded along the whole way and closed the laptop satisfied. And then a week later I sit down to use the thing and discover I have almost nothing. The understanding I was so sure of has evaporated, and I am back at the start. That feeling is a liar, and learning to distrust it is the most useful study skill I have. Here is the method I use instead, the one that actually retains hard technical material, and a warning up front: it feels worse the entire time it is working. ## The gap between feeling fluent and actually knowing The comfortable way to study is to read, highlight, maybe reread the highlights, and let the growing sense of familiarity feel like progress. It is pleasant. It is also mostly useless, and the reason is subtle. Rereading builds fluency, the easy recognition of material you have seen before. Fluency feels exactly like knowledge from the inside. But recognising something when it is in front of you is a completely different skill from producing it when it is not, and only the second one is what you need when you sit down to solve a problem or write a circuit. You can reread an explanation ten times, feel more confident each time, and be no better at reproducing it on a blank page. The confidence grows. The actual ability does not. That gap is where most study time quietly disappears. ## The one principle: retrieval, not review Everything that works rests on a single finding, and it is worth saying plainly because it is counterintuitive. You build durable memory by pulling information out of your head, not by putting it back in. The effort of retrieving something, of dragging it up from memory when it is not in front of you, is the thing that strengthens it. Researchers call it the testing effect, and it is one of the most robust results in the science of learning. A session spent testing yourself beats a session spent rereading, and not by a little, even though the rereading feels far more productive while you do it. So the shift is from input to output. Stop asking how do I get this into my head. Start asking how do I practise getting it out. Every technique below is just a different way to make yourself retrieve. ## Active recall in practice The core move is almost stupidly simple. Close the source. Take a blank page. Reproduce what you just learned from memory, in your own words, as completely as you can. Then, and only then, open the source and check what you missed. The magic is in what happens while you struggle. The places where you stall, where the next line will not come, where you realise you can state the idea but not actually derive it, those stalls are the whole point. They are your real gaps, made visible. The smooth reread hid them. The blank page exposes them in seconds. For technical material this gets concrete and even better. Do not just recall that there is a Hadamard matrix, write it out. Do not recall that a gate is unitary, re derive why. Do not recall that the answer was fifty fifty, [recompute it by hand](https://tuguidragos.com/measurement-is-not-magic-the-born-rule-by-hand/). Production, not recognition. Every self test at the end of the other things I write is this principle in disguise. ## Recall is not rote memorisation A worry I had at first, and maybe you have it too: this sounds like flashcard cramming, memorising facts I do not understand. It is the opposite, and the distinction matters. Rote memorisation is storing a string of symbols you could not reconstruct if you lost them. Recall, done right, is rebuilding understanding from the pieces, not reciting it from a cache. The test is whether you could regenerate the thing if you forgot the exact words. If I ask you for the Hadamard matrix and you have only memorised the four numbers, that is rote, and it is brittle, because the moment you blank you have nothing. But if you can recall that [a Hadamard turns |0⟩ into an equal superposition](https://tuguidragos.com/a-qubit-is-not-a-coin-under-a-cup/) and reconstruct the matrix from what it has to do, that is understanding, and it survives forgetting the surface. So when you sit at the blank page, do not just try to dredge up the words. Try to rebuild the idea from its reasons. Recall the why, and the what comes back with it. That is the difference between knowledge you can use and a fact that crumbles the first time it is tested under pressure. ## Spaced repetition: review just before you forget Recall once and the memory is stronger, but it still fades. The trick is when you recall again. Memory decays along a curve, fast at first then slower, and the most efficient moment to review something is right as you are about to lose it. Review too soon and you waste effort on something still fresh. Review too late and you are relearning from scratch. Hit the sweet spot, just before forgetting, and each review resets the memory stronger and buys you a longer interval before the next one. You do not have to track this by hand. Spaced repetition software, Anki being the free standard, schedules it for you. You make small cards, you answer them, you tell it whether you got each one, and it decides when to show each card again, stretching the gaps as the memory hardens. The discipline that matters is what you put on the cards. Not everything. The load bearing facts. What is the Hadamard matrix. What does the plus state give when you measure it in the standard basis, the ordinary 0 and 1 basis. What does it mean for a gate to be unitary. Small, atomic, the things everything else leans on. A deck of two hundred sharp cards you actually review beats two thousand you made once and abandoned. ## The Feynman technique: explain it to a beginner This one is recall wearing a different hat, and it is brutal in the best way. Take the thing you think you understand and explain it, out loud or on paper, as if to someone who knows nothing. Simple words. No jargon to hide behind. You will get a few sentences in and hit a spot where you go "and then it sort of just works out," and you wave your hand. That hand wave is the sound of a gap. Plain language is a lie detector for understanding, because jargon lets you gesture at an idea without actually having it, and stripping the jargon away leaves you nowhere to hide. When you hit the hand wave, you have found exactly what to go relearn. Patch it, then try the explanation again from the top. When you can explain it cleanly to an imaginary beginner, with no fog and no jargon crutches, you actually have it. Until then you have the feeling, not the thing. ## Interleaving: mix the topics up The last technique is the one that feels most wrong. The intuitive way to study is in blocks. All of the matrix algebra, then all of the Python, then all of the quantum. It feels focused and orderly. It is also weaker than the alternative, which is to mix them. Do a bit of matrix algebra, then a Python snippet, then a circuit, then back to a different bit of algebra, all in one session. Interleaving feels harder because it is harder. Every time you switch, you have to reload the context and, crucially, decide which approach this problem even needs. And that deciding is the skill you actually want. Blocked practice lets you run the same method ten times on autopilot, which feels great and teaches you almost nothing about when to reach for it. Interleaved practice forces you to choose the tool, every time, which is exactly what real problems demand, because real problems do not come labelled with the chapter they are from. It is slower and more frustrating in the room. It retains far better out of it. ## Why this matters even more for technical subjects Hard technical material has a property that makes all of this non negotiable. It is hierarchical. Each idea sits on top of the ones before it. Amplitudes need complex numbers. Gates need vectors and matrices. Algorithms need gates and phase. A gap does not stay politely in its place. It cracks everything you try to build on top of it, and worse, it does so invisibly, because you do not feel the missing piece until you are three concepts further on and nothing makes sense and you cannot tell why. Recall is how you catch those gaps while they are still small and local, before they have anything stacked on them. And technical subjects hand you a gift the humanities do not: a ground truth. You can run the code. You can check the matrix in NumPy. Verification is active recall with an answer key that does not flatter you. You recall your understanding, you express it as something runnable, and the machine tells you instantly and without mercy whether you actually had it right. That is the strongest form of the whole method, and it is the habit I keep coming back to in everything I write. Reproduce it, then check it against something that cannot be fooled. Here is that loop on a fact you have already met. Close everything, write the Hadamard from memory, from what it has to do rather than from a snapshot of its four numbers, and then let the check be the grader. ```python import numpy as np # wrote this from memory, source closed H = (1/np.sqrt(2)) * np.array([[1, 1], [1, -1]]) # the answer key that does not flatter: a real gate is unitary, U dagger U = I assert np.allclose(H.conj().T @ H, np.eye(2)), "recall was wrong, go fix it" assert np.allclose(H @ np.array([1, 0]), np.array([1, 1]) / np.sqrt(2)), "H on |0> is not |+>, go fix it" assert np.allclose(H @ np.array([0, 1]), np.array([1, -1]) / np.sqrt(2)), "the minus sign is misplaced" print("recall holds up") ``` Run it. Misremember a sign or a factor and an assert [fails on the spot](https://tuguidragos.com/the-python-you-actually-need-for-quantum-computing/), in red, and you have caught the gap in seconds instead of three concepts downstream. Pass it cold and you know the fact is yours, not merely familiar. Produce, then verify. The whole method, in a few lines. Here is what the compounding looks like in practice, because it caught me. I half learned [complex numbers](https://tuguidragos.com/complex-numbers-are-the-gateway-to-quantum/), the kind of half where you can nod at them but not really wield them. Weeks later, phase made no sense to me, and I could not work out why, because phase is an angle of a complex number and I had never truly owned the complex number. The gap was not where the pain was. The pain was three concepts downstream, in the algorithms, where nothing would click, and the actual hole was back at the foundation I had waved through. Recall at the bottom of the stack would have caught it cheaply. Discovering it at the top cost me far more, because I had to tear back down through everything I had built on the crack to find it. In a hierarchical subject, the bill for a skipped foundation always comes due later, with interest. ## Why it feels worse, and why that is the point Notice the through line. Recall is harder than rereading. Spacing makes you wait until things are difficult again. Interleaving keeps you off balance. Explaining strips away your crutches. Every one of these feels worse than the cozy reread, and that is not a coincidence. The difficulty is the mechanism. Researchers even have a name for it, desirable difficulty, the useful kind of hard. The effort of struggling to retrieve is not a tax on learning. It is the learning. Which leads to an uncomfortable rule of thumb that has served me well: if your studying feels smooth and easy, you are probably just polishing fluency, not building memory. The sessions that feel like work are usually the ones that stick. ## The ways people fake recall without noticing Knowing the method is not the same as doing it, and there are a handful of ways people think they are doing active recall while quietly sliding back into rereading. I have done all of them. The most common is peeking. You hit a stall, the answer will not come, and within two seconds you glance back at the source to remind yourself. That glance kills it. The struggle you cut short was the entire mechanism, and by peeking you converted a hard, useful retrieval into an easy, useless recognition. Sit in the stall. Let it be uncomfortable. Only check after you have written down everything you can, gaps included. The second is mistaking recognition for production. You read a question, think "yes, I know this," and move on without actually producing the answer. But knowing that you know is not the same as being able to say it, and the gap between them is exactly what trips you up later. If you did not write it or say it, you did not recall it. The third is building cards or questions that only ask for recognition, the "is the Hadamard a gate, yes or no" kind, which you can answer without understanding anything. Good questions force production: write the matrix, derive the result, explain the why. And the last one is the quiet killer, rereading your own notes and counting it as review. Review means testing, not revisiting. If your eyes are passing over the answer, you are not reviewing, you are just visiting an old friend who will not be there on the day it counts. ## A weekly routine you can actually run Here is roughly how I assemble it, on weeknights and a longer Sunday, so it is not just theory. A weeknight session is short and has three parts. A little new material, learned actively, not just read. A few minutes of recall on something from earlier in the week, ideally whatever the cards surface. And one quick Feynman pass, explaining the day's idea out loud to no one. Mix the topics rather than spending a whole night on one, so the math, the Python, and the quantum keep brushing against each other. Sunday is the bigger block: a wider review of the week, a proper teach it out loud on the hardest thing, and a pass through the recall cards that are due. Nothing heroic. An hour on a weeknight and a longer Sunday, done consistently, beats a rare marathon every time, because the spacing itself is doing work that cramming cannot. ## How to start tomorrow You do not need an app or a system to begin. Pick one thing you are sure you learned this week. Close everything. Get a blank page. Write it out from memory, completely, as if teaching it. Then check. The gaps you find in the next ten minutes are not a failure. They are the most honest syllabus you will ever get, pointing exactly at what you only thought you knew. Start there, tomorrow, with one idea and a blank page. ## Where I am learning the method Free, mostly. The book Make It Stick is the best single summary of the research behind all of this, and it is written for normal people, not academics. The underlying findings on the testing effect and spacing are easy to look up and surprisingly fun to read. Anki is free on desktop and worth the slightly clunky setup. But the real point is that the method is the multiplier. The content I pour into it, the videos, the courses, the notebooks, is everywhere and mostly free. What turns watching into knowing is the retrieval, and that part is on you. ## Why the method is the moat I will end on the thing that keeps me doing this even when the easy reread is right there and far more tempting. Anyone can read about quantum. The material is free and abundant and getting more so every month, as AI makes shallow explanations cheaper than ever. What is not abundant is retention. The person who actually retained it, who can reproduce the derivation and rebuild the circuit and explain it cold, is rare, and getting rarer precisely as the reading gets easier. Depth is the thing that lasts when the floor keeps dropping. And depth is not what you read. It is what you can still produce a month later, on a blank page, with nothing in front of you. The method is how reading turns into that. It is slower, it feels worse, and it is the entire edge. ### Complex numbers are the gateway to quantum URL: https://tuguidragos.com/complex-numbers-are-the-gateway-to-quantum/ Last updated: 2026-10-03T13:33:23.000Z The last time I used a complex number before this year, I was in high school, and I was fairly sure I would never see one again. They had that flavour of school math that feels invented to fill a textbook. A made up number whose entire job was to be the square root of something that has no square root. Fine. Noted. Never used it once in years of building real software. Then I started quantum, and complex numbers turned out to be the native language. Not a footnote. The actual thing the amplitudes are made of. You cannot read a quantum state without them. So I went back, expecting a slog, and found that the slice you actually need is small and, once you see it the right way, genuinely beautiful. Here is that slice. ## What a complex number actually is Strip away the school baggage and the definition is one line. A complex number is a + bi, where a and b are ordinary numbers and i is a special symbol defined by a single rule: i² = −1\. That is the whole of it. i is just the name we give to a thing that squares to minus one, which no ordinary number does. The useful picture is not algebra, it is geometry. Ordinary numbers live on a line, left to right. Complex numbers add a second axis going up and down, the imaginary direction, and that turns the line into a plane. So a complex number is a point on a plane. The a tells you how far right, the b how far up. 3 + 4i is the point three across and four up. That is it. A complex number is a location, and once you hold that picture, everything else falls into place. It helps to know these were not invented to torment students. Mathematicians ran into them centuries ago while solving ordinary equations, the kind with real answers, where the only way to reach the real solution led through the square root of a negative number along the way. The imaginary part was a bridge you had to cross to get to a perfectly real destination. The name imaginary is a historical insult that stuck, from a time when they were distrusted as not quite real. They are as real as any other number, they just live on a plane instead of a line, and the plane is exactly what makes them able to describe rotation, which is the only reason we care here. ## The one idea that unlocks it: multiplying by i is a quarter turn This is the thing nobody told me in school, and it is the thing that makes complex numbers click for quantum. Multiplying by i rotates a number ninety degrees, counterclockwise, around the origin. Watch it go. Start at 1, sitting on the right. Multiply by i and you get i, which sits straight up. Multiply by i again and you get i², which is −1, sitting on the left. Again, and you are at −i, straight down. One more, and you are back at 1\. Four multiplications by i, four quarter turns, one full circle. ```python for k in range(4): print(1j**k) # (1+0j), 1j, (-1+0j), (-0-1j) ``` Run it. Right, up, left, down. The last line prints a minus zero on the real part, which is a floating point artefact of raising to a power rather than a fourth direction. Multiplying by i is a rotation, full stop. This is why complex numbers and quantum fit together so naturally. Quantum is full of rotations of a state, and complex numbers are the most natural language of rotation there is. The moment I saw i as a quarter turn instead of a mysterious symbol, half the strangeness drained out of the whole subject. ## Doing the arithmetic, and what it means geometrically Adding and multiplying complex numbers is mechanical, but each operation has a picture, and the pictures are the part worth keeping. Adding is the easy one: you add the real parts and the imaginary parts separately, which geometrically just slides one point by the other, the same way you add arrows. (2 + 3i) + (1 + i) is 3 + 4i. Nothing surprising. Multiplying is where it gets interesting. You expand it like any pair of brackets, remembering that i² is −1: ```python print((2 + 3j) * (1 + 1j)) # (-1+5j) ``` Run it. You get −1 + 5i. But the mechanical answer hides the beautiful part. Multiplying two complex numbers rotates and scales. The angles add, and the lengths multiply. Multiplying by i, which sits at ninety degrees with length one, adds ninety degrees and leaves the length alone, which is exactly the quarter turn from before. That was just the special case. In general, every complex multiplication is a turn by one number's angle and a stretch by its length. Hold onto that, because it is the single idea that makes the next form click. ## Length and angle: the polar form If multiplication is really about angles and lengths, then the natural way to write a complex number is not right and up, but length and angle. This is the polar form: every complex number is r times e^(iθ), where r is the length and θ is the angle. Do not worry about the e yet. For now read e^(iθ) as a label that means turned by the angle θ, and I will show you in two sections what that label actually equals. The point 3 + 3i, for instance, sits at forty five degrees with a length of about 4.24. ```python import cmath, numpy as np r, theta = abs(3 + 3j), cmath.phase(3 + 3j) print(round(r, 3), round(np.degrees(theta), 1)) # 4.243 45.0 ``` Run it. Length 4.24, angle 45 degrees, the same point seen in terms of how far and which way instead of across and up. And in this form, the rule for multiplication becomes almost trivial: to multiply, add the angles and multiply the lengths. Two points each at e^(iπ/4), forty five degrees written in radians, multiply to one at e^(iπ/2), ninety degrees, because the angles simply add. ```python print(np.round(np.exp(1j*np.pi/4) * np.exp(1j*np.pi/4), 10)) # 1j = e^{i pi/2} ``` Run it. Two eighth turns make a quarter turn, by addition of angles, just as you would hope. This is the form quantum lives in, because an amplitude is most naturally described by a length, which squared becomes a probability, and an angle, which is the phase. Polar form is not extra theory. It is the form the physics actually wants. ## The modulus is the length, and the length becomes probability If a complex number is a point on a plane, it has a distance from the origin, and that distance has a name: the modulus, written |z|. You get it the way you get any distance, with the Pythagorean theorem. For z = a + bi, the modulus is the square root of a² + b². ```python print(abs(3 + 4j)) # 5.0 ``` Run it. The point 3 + 4i is exactly five units from the origin, a three four five triangle hiding in the plane. Now here is why you care. In quantum, the amplitudes are complex numbers, and the probability of an outcome is the modulus of its amplitude, squared. That is the Born rule again, the one I worked out by hand in the post on why [measurement is not magic](https://tuguidragos.com/measurement-is-not-magic-the-born-rule-by-hand/). So the modulus is the bridge. It is the operation that takes a complex amplitude, which you cannot measure directly, and turns it into a real probability, which you can. Every histogram you read is moduli, squared. ## Euler's formula: the unit circle in one line Now I can pay off the e I asked you to take on trust. The polar form gave you length and angle, and it is the form quantum actually uses most, but it left e^(iθ) sitting there as a label with no meaning attached. Euler's formula is what that label equals: e^(iθ) = cos θ + i sin θ Do not let the e scare you. The useful reading is pure geometry. e^(iθ) means stand on the unit circle and turn by the angle θ. At θ = 0 you are at 1, on the right. At θ = π/2, a quarter turn, you are at i, straight up. At θ = π, a half turn, you are at −1. ```python import numpy as np print(np.round(np.exp(1j*np.pi), 10)) # -1, a half turn print(np.round(np.exp(1j*np.pi/2), 10)) # i, a quarter turn ``` Run it. A half turn lands you at minus one, a quarter turn at i, exactly matching the rotation picture from before. This is the famous identity people put on t shirts, e to the i pi equals minus one, but the t shirt hides the point. The point is that e^(iθ) is just a clean way to name a direction on the circle. And that angle θ has a name you will hear constantly. It is the phase. ## Why this is suddenly the thing you measure Here is where it all pays off, and why real numbers simply will not do for quantum. A complex amplitude carries two pieces of information at once. Its modulus, the length, which becomes the probability when you square it. And its angle, the phase, which is the direction it points on the circle. One number, two facts. When you measure, you only see the first one. The probability comes from the modulus, and the phase is invisible to a single measurement. So you might think the phase does not matter. It matters enormously. The phase is what the next gate sees. Two amplitudes with the same length but different phases will behave completely differently when they meet, reinforcing or cancelling, and that cancellation is interference, which is the real reason [a qubit is not a coin under a cup](https://tuguidragos.com/a-qubit-is-not-a-coin-under-a-cup/). I will give phase its own post, because the difference between phase that matters and phase that does not is one of the real hinges of the subject. For now, the takeaway is this. The phase is real, it is carried by the angle of a complex number, and no real number could hold it. That is why quantum is built on complex numbers and not the ones I learned to count with. They are the only objects that carry magnitude and phase together. Make it concrete with a real amplitude. Suppose one outcome has amplitude e^(iπ/4) divided by √2\. In polar form that is length 1/√2 at an angle of π/4\. The probability is the length squared: ```python import numpy as np amp = np.exp(1j*np.pi/4) / np.sqrt(2) print(round(abs(amp)**2, 6)) # 0.5 ``` Run it. The probability is one half, set entirely by the length, while the angle π/4 sits there as the phase, doing nothing to the odds but waiting to matter the instant another gate acts. Two states could both give an outcome a probability of one half and yet be completely different states, because their phases differ. The histogram cannot tell them apart. The next gate can. That gap between what the probability shows and what the amplitude actually holds is the whole reason the phase is worth caring about. ## The conjugate, briefly One small operation rounds out the slice. The conjugate of a + bi is a − bi. You just flip the sign of the imaginary part, which geometrically is a mirror flip across the horizontal axis. ```python print((2 + 3j).conjugate()) # (2-3j) ``` Run it. Two reasons it shows up. First, the modulus squared is a number times its own conjugate, which is the tidy way the probability actually gets computed. Second, the inner product from the measurement post conjugates the first state, the bra, and now you know exactly what that conjugation is doing. It is this mirror flip, nothing more. ## Where this lands: a qubit is two complex numbers Now connect the whole slice back to the thing we are actually here for. A qubit's state is written α|0⟩ + β|1⟩, and α and β, the amplitudes, are complex numbers. That is it. A qubit is, mathematically, a pair of complex numbers, tied together by the rule that their squared lengths add to one. Everything in this post was really about understanding those two numbers. Each amplitude carries a length and an angle, as we have seen. The lengths, squared, are the probabilities of measuring 0 or 1\. The angles are the phases. And here is the subtle part that the complex view makes obvious. What matters physically is not the two angles on their own, but the difference between them, the relative phase. If you turned both amplitudes by the same angle, every probability would stay identical and so would every future measurement, so that shared turn, the global phase, is invisible and physically meaningless. But a difference in phase between the two amplitudes is very real, and it is exactly what gates exploit to make interference happen. I promised a whole post on this a few sections up, and now you can see why it earns one. The difference between a phase that is global and therefore invisible, and a phase that is relative and therefore everything, is a distinction you can only even state once you see a qubit as two complex numbers with two angles. The complex numbers are not decoration on top of the physics. They are the structure that makes the physics expressible at all. ## The slice you need, and what to skip So here is the honest boundary, the same way I drew it for [the Python you actually need for quantum](https://tuguidragos.com/the-python-you-actually-need-for-quantum-computing/) and for the rest of the math. You need four things. The picture of a complex number as a point on a plane. Multiplication by i as a quarter turn. The modulus, because squared it is your probability. And Euler's form e^(iθ), because the angle is the phase. The conjugate is a small bonus that makes the modulus and the inner product cleaner. That is the entire complex number toolkit for getting started in quantum. What you can skip, possibly forever, is everything that made complex analysis a hard university course. Contour integrals. Residues. Analytic functions. The deep theory is gorgeous and you will not touch it to read a quantum state. As I keep saying, the integrals can wait. The slice is small on purpose, and the small slice is the part that is actually beautiful anyway. ## A quick test before you move on Close this and answer in your own words. What does multiplying by i do to a number, geometrically? If your answer is not turns it ninety degrees, go back to the rotation section, because that one idea carries the most weight. If an amplitude is 1/√2 times e^(iθ), which part becomes the probability and which part is the phase? If you cannot split the magnitude from the angle, that is the gap. And why can a real number not do an amplitude's job? If the words magnitude and phase together are not in your answer, reread the section on why you measure this. ## Where I am learning it Free and, for this topic especially, worth it for the visuals. 3Blue1Brown has the clearest videos I have found on what a complex number is and on Euler's formula, and seeing i rotate a point in an animation did more than any page of algebra ever did for me. Khan Academy covers the mechanics if you want to drill the arithmetic until it is automatic. And as always, I keep a NumPy file where I check every one of these by hand, because a rotation I have only watched is not a rotation I trust until I have made the number move myself. ## The detour that was the destination I spent years thinking complex numbers were a piece of school math I had safely left behind. A clever trick with no use outside an exam. It turns out they were not a detour at all. They are the language the whole subject is written in, the only numbers that can hold a magnitude and a direction at the same time, which is exactly what a quantum amplitude is. The square root of minus one stopped being a riddle the moment I saw it as a quarter turn. And the thing I was sure I would never use again turned out to be the gateway. Funny how often that is how it goes. ### The version trap that cost me an evening (Qiskit 2.x) URL: https://tuguidragos.com/the-version-trap-that-cost-me-an-evening-qiskit-2-x/ Last updated: 2026-10-03T13:34:19.000Z I lost an evening to four lines of code that were completely correct. That is the part that stung. Not a typo, not a logic error, nothing I could have spotted by reading harder. The lines were correct. They were just correct for a version of Qiskit that no longer exists. If you are learning quantum in 2026, this will happen to you, probably more than once. So here is the trap, laid out, with the time I already paid so you do not have to. ## The evening I lost I was following along with a tutorial. Clean, well written, the kind that makes you feel like you are in good hands. It built a small circuit and ran it like this: ```python from qiskit import execute job = execute(qc, backend, shots=1024) result = job.result() counts = result.get_counts() ``` I ran it. It died on the first line. ``` ImportError: cannot import name 'execute' from 'qiskit' ``` So I did what you do. I assumed it was me. I checked my spelling. I reinstalled things. I read the line ten times. I went looking for what I had broken in my environment, because the code was obviously right, it was right there in a tutorial, so the problem had to be on my end. It was not on my end. The function that whole example is built around, `execute`, does not exist in modern Qiskit. It was removed. The tutorial was not wrong when it was written. It was just written for a different era, and nothing on the page told me that. That is the trap in one sentence. The code is not buggy. It is expired. ## Why quantum has a worse version problem than most Every library changes over time. This one changed hard. Qiskit 1.0, in early 2024, was a deliberate clean break, and a lot of the old, comfortable ways of doing things were swept out in it and the versions after. I am on 2.x now, and whole patterns that filled tutorials two years ago simply do not run anymore. The reason it bites harder in quantum than in, say, web development is volume. When a popular web framework changes, the internet floods with new tutorials within weeks, and the old ones sink. Quantum is a smaller world. There is far less content overall, the old material does not get buried, and it keeps showing up at the top of your search results and inside the AI you ask for help, looking exactly as authoritative as it did the day it was correct. So you reach for the obvious example, and the obvious example is from before the break. I wrote a whole separate post about [not letting an AI tutor feed you dead code](https://tuguidragos.com/how-i-use-ai-as-a-tutor-without-letting-it-lie-to-me/). This is that dead code, in the flesh. Here is exactly what to write instead. ## What is actually dead, and what replaced it A short, honest map of what I have hit so far on 2.x. **execute is gone.** The single entry point everyone used to run a circuit was removed. There is no drop in rename. The whole flow around it changed. **Aer and BasicAer no longer hang off the main package.** Code that starts with `Aer.get_backend('qasm_simulator')` assumes a convenience that modern Qiskit does not give you from the top level. The high performance simulator now lives in its own package, `qiskit_aer`, which you install and import on its own. **The run and get\_counts flow gave way to primitives.** Instead of one `execute` call, you now go through a small object called a primitive. There are two you will meet constantly. A Sampler, when you want measurement outcomes, the counts. An Estimator, when you want an expectation value, that single summary number. They are the modern front door, and once you know they exist, the new examples stop looking foreign. **The shape of the result changed too.** The old `result.get_counts()` became [a short chain that reaches into the result](https://tuguidragos.com/the-python-you-actually-need-for-quantum-computing/) and pulls the counts out of a named register. That is the part that surprises people even after they fix the imports, so it gets its own section below. ## The modern minimal pattern Here is the smallest circuit that actually runs on a current Qiskit, end to end, with nothing deprecated in it. This is the version of those four tutorial lines that does not expire on you. ```python from qiskit import QuantumCircuit from qiskit.primitives import StatevectorSampler qc = QuantumCircuit(1, 1) # one qubit, one classical bit to store the result qc.h(0) # put it in superposition qc.measure(0, 0) # measure qubit 0 into classical bit 0 sampler = StatevectorSampler() # Qiskit's own built in simulator, no Aer needed result = sampler.run([qc], shots=1024).result() counts = result[0].data.c.get_counts() print(counts) # {'0': ~512, '1': ~512} ``` Run it. You get roughly five hundred of each, the equal superposition you would expect. Notice the shape. You hand the sampler a list of circuits, because you can run several at once. You get back a result you index into with `[0]` for the first circuit. And the counts come out of `result[0].data.c`. That `.c` is the name of your classical register, and it is the single most common thing people trip on after they fix the imports, so here it is on its own. ## The gotcha that gets everyone: which register name When you read the counts, you have to name the classical register you measured into, and the name depends on how you created it. This is not in most old tutorials, because the old flow did not work this way. If you build the circuit with an explicit classical register, as above with `QuantumCircuit(1, 1)` and `measure(0, 0)`, the register is called `c` by default. So you read `result[0].data.c.get_counts()`. But there is a popular shortcut, `measure_all()`, that adds measurements to every qubit in one call. It is convenient, and it creates its own register, named `meas`. So if you used it, the same line becomes `result[0].data.meas.get_counts()`, with `meas` instead of `c`. ```python qc = QuantumCircuit(1) qc.h(0) qc.measure_all() # convenient, but it names the register 'meas' result = StatevectorSampler().run([qc], shots=1024).result() counts = result[0].data.meas.get_counts() # .meas, not .c print(counts) ``` Run it. Same outcome, different attribute name. Get the name wrong and you get an error that does not obviously say register name, so you sit there confused. Now you know. The name you read is the name you created. If you did not name it, it is `c` with an explicit register, and `meas` with `measure_all`. ## Sampler gives you counts, Estimator gives you a number While we are here, it is worth knowing the second primitive, because half the modern examples use it and it confused me at first. The Sampler above gives you measurement outcomes, the counts dictionary. But often you do not want the full distribution, you want a single summary number, the expectation value from [the measurement post](https://tuguidragos.com/measurement-is-not-magic-the-born-rule-by-hand/). That is what the Estimator is for. You hand it a circuit and an observable, the thing you want the average of, and it gives you back one number. ```python from qiskit import QuantumCircuit from qiskit.primitives import StatevectorEstimator from qiskit.quantum_info import SparsePauliOp qc = QuantumCircuit(1) qc.h(0) # |+>, which leans neither way observable = SparsePauliOp("Z") # measure the Z expectation result = StatevectorEstimator().run([(qc, observable)]).result() print(float(result[0].data.evs)) # 0.0 ``` Run it. You get zero, exactly the balanced result for |+⟩ that the Born rule post worked out by hand. Notice there is no measure on the circuit and no counts. The Estimator handles the averaging for you and returns the number directly. So the rule of thumb is simple. Want the outcomes, the histogram? Sampler, with a measurement on the circuit. Want one expectation value? Estimator, with an observable and no measurement. Mixing those two up is a common early stumble, and now it is not one for you. ## On real hardware, your circuit gets translated first One more piece of the modern flow that the old `execute` call hid from you. A real quantum computer does not understand every gate you write. Each device has its own small set of gates it can physically perform, its native set, and your circuit has to be rewritten into only those gates before it can run. That rewriting is called [transpilation](https://tuguidragos.com/quantum-circuits-ibm-hardware-n8n/), and in modern Qiskit you do it explicitly. ```python from qiskit import QuantumCircuit, transpile qc = QuantumCircuit(2) qc.h(0) qc.cx(0, 1) # rewrite the circuit using only a small native gate set, here a hand written list native = transpile(qc, basis_gates=["rz", "sx", "x", "cx"], optimization_level=1) print(sorted({instr.operation.name for instr in native.data})) # ['cx', 'rz', 'sx'] ``` Run it. The Hadamard you wrote is gone, replaced by a combination of the device's actual gates that does the same thing. This is not optional on real hardware. The transpiler also tries to optimise the circuit and to map your abstract qubits onto the physical ones, which on a noisy machine matters a great deal. The old `execute` did some of this silently. The modern flow asks you to do it on purpose, which is more code but far less mystery, because you can see exactly what your circuit became before it ran. ## How I actually found the fix It is worth walking the diagnosis, because the process generalises to every expired pattern you will ever hit, not just this one. When the `ImportError` came up, the move that finally worked was boring and methodical. First, I read the error literally instead of panicking. Cannot import name `execute` from qiskit means exactly what it says: that name is not there. Not misspelled, not misconfigured, not there. That alone ruled out half of what I had been checking. Second, I printed my version, the one line I show in the next section, and confirmed I was on 2.x, well past the break. Third, instead of searching the open web, where I would just find more of the same expired tutorials, I went to the [official migration guide](https://quantum.cloud.ibm.com/docs/en/guides/qiskit-1.0-features) and searched for execute. It told me directly that the function was removed and pointed me at the primitives that replaced it. Ten minutes, start to finish, once I stopped flailing and followed the trail. The lesson is not about this one function. It is the routine. Read the error as fact, confirm the version, go to the versioned docs, find what the old thing maps to. That sequence turns a confusing wall into a quick lookup, and it works for every removed function you will meet, because the migration guides are written precisely to answer the question "this used to exist, what do I do now." ## How to stop losing evenings The fixes are boring and they work. **Print your version, always.** One line, import qiskit then `print(qiskit.__version__)`, at the top of anything you are debugging. Half of these mysteries evaporate the moment you know exactly which Qiskit you are talking to. I now do it reflexively, the way I check which environment a deploy is pointing at before I panic about a broken pipeline. **Pin it.** Write the version down in your project so it does not silently move under you between sessions. Future you, three weeks from now, wondering why the same code behaves differently, will be grateful. **Run it before you trust it.** This is the same rule from the AI post, and it is the one that actually saves you. A correct looking example proves nothing. A running example proves everything. The interpreter does not care how authoritative the source looked. **Go to the versioned source, not the first result.** The official documentation is versioned and kept current with the library, so it ages far better than a two year old blog post or a top voted answer from another era. A random blog post is not versioned, and an AI answer is dated to whenever it was trained. When something will not run, I go to the official docs first now, check that the page matches the version I printed, and only widen the search if the official material does not cover what I need. That one habit quietly removes most of the expired code from your life, because you stop pulling it in to begin with. ## Deprecation is a warning. Removal is a wall. One distinction worth carrying, because the two feel different and you handle them differently. A deprecation warning means the thing still works, for now, but it is on its way out. Your code runs, and a yellow message tells you to stop using that function before a future version deletes it. Treat those warnings as a to do list, not noise. The version that turns them red is coming. A removal is the wall I hit. The thing is simply gone, and you get a hard error, today, with no grace period. `execute` is past the warning stage. It is a wall now. The lesson is to act on deprecation warnings while they are still just warnings, so you are never the person discovering a removal the hard way on a deadline. ## The discipline you already have Here is the thing. As a builder, I already live this lesson everywhere else. I pin dependencies. I check which version is running before I assume the logic is wrong. I know that "it worked in the tutorial" means nothing if the tutorial is from another year. None of this was new wisdom. I just forgot, for one evening, that quantum is software, and software rots, and a fast moving young library rots faster than most. So I treat Qiskit the way I treat every other dependency in every other system I have ever shipped. Know the version. Trust the running code, not the screenshot. Read the changelog. The physics is the exotic part. The version trap is just ordinary engineering, and you already know how to handle it. You just have to remember that it applies here too. If you carry one model out of this, make it this. Modern Qiskit has a small, stable shape: you build a circuit as an object, you transpile it for wherever it is going to run, and you execute it through a primitive, a Sampler for outcomes or an Estimator for an expectation value, then you read the result out of a named register, or a single number from the estimator. Build, transpile, run through a primitive, read. Every current example fits that shape, and anything that does not, anything reaching for `execute` or pulling `Aer` off the top level package, is from before the break and will fight you. Once the shape is in your head, you can spot expired code on sight, which is a quieter superpower than it sounds. You stop copying the broken examples in the first place, and you stop losing evenings to code that was never going to run. ### Measurement is not magic: the Born rule by hand URL: https://tuguidragos.com/measurement-is-not-magic-the-born-rule-by-hand/ Last updated: 2026-10-03T13:34:18.000Z Earlier in this series I made a case that [a qubit is not a coin under a cup](https://tuguidragos.com/a-qubit-is-not-a-coin-under-a-cup/). It is a vector with two amplitudes, turning under exact rotations, undecided about nothing, right up until you look. Which leaves the obvious question hanging. Fine. So what exactly happens when you look? That is measurement, and it is the step everyone treats as the mystical one. The wave function collapses. The universe decides. Cue the dramatic music. I needed something I could actually use, so I went looking for the mechanism, and it turned out to be one short formula. Once I could run it by hand, measurement stopped being the magic step and became, in aggregate, the most predictable thing in the whole machine. Here is that formula, and everything that falls out of it. ## The whole rule, in one line This is the Born rule, and it is the most important equation in quantum computing. Every histogram you will ever stare at is this, and nothing but this. P(k) = |⟨k|ψ⟩|² In words. The probability of getting outcome k when you measure the state |ψ⟩ is the squared size of the overlap between k and ψ. That overlap, the ⟨k|ψ⟩ part, is a single number called the inner product. Take its magnitude, square it, and you have your probability. You have already met the easy version of this. When I wrote that a qubit α|0⟩ + β|1⟩ gives 0 with probability |α|² and 1 with probability |β|², that was the Born rule in its simplest case, where the thing you measure is one of the basis states. The general formula above is the same idea with the training wheels off. It works for any measurement, not just the obvious one, and that generality is where measurement gets genuinely interesting. So before anything else, I had to actually understand that ⟨k|ψ⟩ object. ## The inner product is just overlap The inner product takes two states and returns one number. You multiply them component by component and add up the results. The only twist is that the first one, the part written as a bra ⟨ |, gets conjugated, which for real numbers changes nothing and just gives you the ordinary dot product you already know. What it means is the useful part. The inner product measures how much two states overlap. If it is zero, the states are orthogonal, completely distinct, perfectly distinguishable. If its size is one, they are the same state. Everything in between is partial overlap. The two facts you lean on constantly: ⟨0|0⟩ = 1, a state fully overlaps with itself. ⟨0|1⟩ = 0, the states |0⟩ and |1⟩ are orthogonal, no overlap at all. Let me do one that is not just 0 or 1, by hand, because it is worth seeing once. Take the overlap of |0⟩ with the superposition |+⟩ = (|0⟩ + |1⟩)/√2\. Writing |0⟩ as the column \[1, 0\] and |+⟩ as \[1/√2, 1/√2\], the inner product multiplies matching entries and adds them: 1 times 1/√2, plus 0 times 1/√2, which is just 1/√2\. Square that and you get one half. So the chance of measuring 0 from |+⟩ is one half, computed straight from the overlap. The same arithmetic in NumPy, as a check: ```python import numpy as np s = 1/np.sqrt(2) ket0 = np.array([1, 0]); plus = s*np.array([1, 1]) print(np.vdot(ket0, plus)) # 0.7071... the overlap <0|+> print(round(abs(np.vdot(ket0, plus))**2, 12)) # 0.5 the probability ``` Put that together with the Born rule and a clean sentence falls out, the one that finally made measurement make sense to me. |⟨k|ψ⟩|² is the probability of finding your state behaving like k when you measure. It is overlap, squared, turned into odds. Measurement asks how much does my state look like this outcome, and the answer, squared, is how often you get it. ## Measurement depends on the question you ask Here is the part that is genuinely deeper than amplitudes squared, and the part most explanations skip. Take the equal superposition from earlier, the state |+⟩ = (|0⟩ + |1⟩)/√2\. Measure it in the ordinary 0 and 1 basis. The overlaps are each 1/√2, so each probability is one half. Fifty fifty. Exactly the quantum coin everyone expects. ```python import numpy as np s = 1/np.sqrt(2) ket0 = np.array([1, 0]) ket1 = np.array([0, 1]) plus = s * np.array([1, 1]) # |+> minus = s * np.array([1, -1]) # |-> # np.vdot(a, b) conjugates a, so it is exactly print(round(abs(np.vdot(ket0, plus))**2, 12)) # 0.5 P(0) print(round(abs(np.vdot(ket1, plus))**2, 12)) # 0.5 P(1) ``` Run it. Both come out to one half, as promised. So far, coin. Now measure the very same state in a different basis, the plus and minus basis, the one built from |+⟩ and |−⟩. Nothing about the qubit changes. Only the question changes. ```python print(round(abs(np.vdot(plus, plus))**2, 12)) # 1.0 P(+) print(round(abs(np.vdot(minus, plus))**2, 12)) # 0.0 P(-) ``` Certain. In this basis the same state gives |+⟩ every single time, with zero chance of |−⟩. The fifty fifty was never a property of the qubit. It was a property of the question I happened to ask it. This is what I meant earlier when I said a superposition is a definite state, not an uncertain one. |+⟩ is not unsure of itself. It is pointing in a perfectly definite direction. It only looks random when you insist on measuring it along the 0 and 1 axis, which is not the axis it is pointing along. Ask it the right question and it answers without hesitation. Measurement does not reveal a hidden value the qubit was secretly holding. It compares the qubit against whatever outcomes your chosen measurement offers, by overlap, and then rolls the dice the Born rule hands it. ## How you actually measure in another basis There is a catch I glossed over, and it is the part that connects measurement back to gates. Real hardware only knows how to measure in one basis, the ordinary 0 and 1 one. You do not get a dial for measuring in the plus and minus basis instead. So how do you ever ask a different question? You move the question to the measurement, instead of the other way around. Before you measure, you apply a gate that rotates the basis you care about onto the 0 and 1 axis. Then you measure normally, and you relabel the outcomes. For the plus and minus basis, that rotating gate is the Hadamard. ```python import numpy as np s = 1/np.sqrt(2) H = s*np.array([[1, 1], [1, -1]]) plus = s*np.array([1, 1]) print(np.round(H @ plus, 12)) # [1. 0.] -> |0>, a certain result ``` Run it. The Hadamard turns |+⟩ into plain |0⟩, so now an ordinary measurement gives 0 every time, which you read as the |+⟩ outcome. That is exactly the certainty we computed with the inner product, arrived at a second way. Measuring |+⟩ in the plus and minus basis and applying a Hadamard then measuring in the normal basis are the same experiment. This is why so many circuits end with a gate sitting right before the measurement. That last gate is not part of the computation. It is choosing which question you are about to ask the qubit. Once I saw that, the structure of real circuits clicked. The middle does the work, and the gate at the end picks the basis you read out in. ## One measurement tells you almost nothing A consequence that confused me until I sat with it. A single measurement is nearly useless. Measure once and you get one outcome. A 0 or a 1\. From that one bit you cannot recover the amplitudes, you cannot see the probabilities, you learn almost nothing about the state you prepared. The amplitudes are really there, but measurement does not report them. It samples from them, once, and hands you the single result it drew. So you do the only thing you can. You run the whole thing again. And again. Thousands of times. Each run, called a shot, gives you one more sample, and as the samples pile up, their proportions close in on the true probabilities. ```python import numpy as np rng = np.random.default_rng(0) # 10000 measurements of |+> in the 0/1 basis: each is 0 or 1, fifty fifty shots = rng.choice([0, 1], size=10000, p=[0.5, 0.5]) values, counts = np.unique(shots, return_counts=True) print(dict(zip(values.tolist(), counts.tolist()))) # roughly {0: 5000, 1: 5000} ``` Run it. You get something close to five thousand of each, drifting closer to the true split the more shots you take. An exact five thousand split does happen, just rarely: the probability is about 0.8 percent, roughly one run in 125\. This is exactly why a quantum result comes back as [a dictionary of counts](https://tuguidragos.com/the-python-you-actually-need-for-quantum-computing/) rather than a clean probability. The hardware cannot tell you the probability directly. It can only let you sample, so you sample a lot and read the proportions off the histogram. That counts dictionary you get from Qiskit is just this experiment, run on real qubits. How many shots is enough is its own small art. The wobble around the true value shrinks slowly, roughly with the square root of the number of shots, which means to halve your uncertainty you need four times as many runs. A thousand shots pins a fifty fifty split down to a percent or two. Chasing a tiny difference between two outcomes can take far more. This is the quiet tax of a probabilistic machine. You are never reading an answer, you are estimating one, and a sharper estimate costs real time on the hardware. It is the same reason a poll of a few thousand people has a margin of error, and the same math sets it. ## The expectation value, or the number you actually report Often you do not even want the full distribution. You want one summary number, and there is a standard one. The expectation value is the long run average of the outcomes, each outcome weighted by how likely it is. E\[X\] = the sum of each outcome times its probability On a fair die, that is 1 through 6 each with probability one sixth, which averages to 3.5\. The die never lands on 3.5\. It is just where the average settles. In quantum you do the same thing with a small convention. Label the outcome |0⟩ as +1 and the outcome |1⟩ as −1\. The expectation value of that labelling is written ⟨Z⟩, and it tells you, in a single number, which way the qubit leans. ```python import numpy as np ket0 = np.array([1, 0]); ket1 = np.array([0, 1]) s = 1/np.sqrt(2) for name, state in [("|0>", ket0), ("|+>", s*np.array([1, 1])), ("|1>", ket1)]: p0 = abs(np.vdot(ket0, state))**2 p1 = abs(np.vdot(ket1, state))**2 print(name, " =", round((+1)*p0 + (-1)*p1, 12)) ``` Run it. |0⟩ gives +1, a guaranteed up. |1⟩ gives −1, a guaranteed down. And |+⟩ gives exactly 0, perfectly balanced between the two, which is the number's way of saying fifty fifty. This is what Qiskit's estimator primitive computes for you, and it is what an enormous number of quantum algorithms actually read out at the end. Not a full histogram. One expectation value. Worth doing the |+⟩ case by hand, since it is one line and it demystifies the symbol. For |+⟩ the two probabilities are each one half. So ⟨Z⟩ is (+1) times one half, plus (−1) times one half, which is one half minus one half, which is zero. The qubit leans neither way. A result of +1 would mean certainly 0, a result of −1 would mean certainly 1, and everything between is a lean in one direction. That single number captures the balance of the whole distribution. This matters because a whole family of important algorithms is built to be read this way. The ones people are actually trying to run on today's noisy hardware, the chemistry and optimisation routines with names like [VQE](https://tuguidragos.com/three-quantum-ways-to-diagonalize-a-giant-matrix/), work by adjusting a circuit to push an expectation value as low as it will go. They never ask for the full histogram. They ask for one number, over and over, and steer by it. So the humble weighted average is not a side note. For a large slice of near term quantum, it is the entire output. ## And then the state is gone The last piece, and the one with real consequences for how you write circuits. Measurement is not a peek. It is destructive. The moment you measure |+⟩ and get a 0, the qubit is now |0⟩, full stop. The superposition you carefully built is spent. Measure it again and you will get 0 again, because there is no superposition left to sample, just a plain 0 sitting there. This is why you cannot prepare one state and measure it a thousand times to gather your statistics. The first measurement destroys what you were measuring. Each shot has to start over, build the state from scratch, then measure once. Run the circuit, sample once, throw the qubit away, rebuild, repeat. When you ask Qiskit for a thousand shots, that is the loop it is running. A thousand fresh preparations, a thousand single measurements, collected into the counts. Once that landed, the whole shape of a quantum program made sense. Build, measure once, the state is gone. The only way to learn about a state is to make many runs of the same experiment and look at the spread. You might wonder why you cannot just copy the state before measuring, keep the original safe, and measure the copies. It turns out you cannot. There is a theorem, called no cloning, that says an unknown quantum state cannot be duplicated, full stop. That is not an engineering limitation waiting to be solved. It is a law of the place. It is also one of the deepest facts in the whole subject, and it is why rerunning the experiment from scratch is not just the easy option, it is the only one. I will give no cloning its own post later, because it quietly shapes far more than measurement. ## A quick test before you move on Close this and answer, in your own words. What does ⟨k|ψ⟩ mean, in plain language, before you square it? If your answer is not something like how much the state overlaps with that outcome, go back to the inner product section. Why is the same state |+⟩ random in one basis and certain in another? If you cannot tie that to the question you are asking rather than the qubit changing, that is the gap. And why does a quantum result come back as a dictionary of counts instead of a probability? If the words sample, shots, and one measurement tells you almost nothing are not in your answer, reread that part. The places you stalled are the parts worth your next session. Not the parts that read smoothly. ## Where I am learning it Free and worth it. 3Blue1Brown again, for seeing the inner product as overlap rather than a formula. Khan Academy for the probability basics, expectation values and distributions, if that corner is rusty. And the same habit as always, I run every one of these in NumPy until the numbers and the words agree, because a probability I only read about is not a probability I trust. ## The thing behind the thing Measurement was the part I was most ready to find mystical. The collapse, the universe choosing, the dramatic music. Underneath the slogan was a single formula about overlap, a coin you have to flip thousands of times to read, and a state that vanishes the instant you look at it. A quantum result is not magic appearing out of the void. It is the Born rule, sampled enough times to become a histogram you can actually read. The mystery was just an equation I had not run yet. So I ran it. ### My IBM Quantum node for n8n is now live URL: https://tuguidragos.com/ibm-quantum-node-for-n8n/ Last updated: 2026-09-13T17:34:16.000Z The email landed at eight in the morning. n8n had verified my community node for the IBM Quantum Platform and published it on n8n Cloud. From that moment, anyone on n8n Cloud, or on a recent self hosted instance, can open the editor, type Quantum into the node search, and wire the IBM Quantum Platform straight into a workflow. This blog runs on two threads. One is quantum computing, which I am learning in public, one post at a time. The other is automation, which is the work I do with my hands every day. Until this node, they ran beside each other here without ever touching. [This node](https://tuguidragos.com/quantum-circuits-ibm-hardware-n8n/) is the first place they become a single thing. ## The package ships three nodes It is not one node bolted on. It is a small, deliberate set, and each one has a job. - **Build, run, and retrieve circuits.** The main node talks to the IBM Quantum Platform. You hand it a circuit, it submits the job, and it brings the results back into the workflow as data you can read, branch on, or pass downstream. - **A trigger for finished jobs.** Quantum jobs are not instant. This trigger starts a workflow at its first poll after a job finishes, whether it completed, failed, or was cancelled, so you never have to sit and poll for a status. - **A trigger for failures only.** A separate error trigger fires when a job fails or is cancelled, and it hands you the failure reason and the error code. That is exactly what you want when you are building something that has to react to trouble on its own. To use any of them you connect an IBM Quantum Platform credential, the same API key you would use from a Python notebook. Nothing exotic. If you can run a circuit from a script, you can run one from here. ## You add it from the canvas, on Cloud and self hosted Here is the part that makes this feel different from an ordinary community node. ![The n8n nodes panel with Quantum typed into the search box, showing three IBM Quantum community nodes under the More from the community section: a trigger for finished jobs, a tool to build and run circuits, and an error trigger.](https://tuguidragos.com/content/images/2026/06/n8n-cloud.png) Searching the nodes panel on my own n8n Cloud instance. The three IBM Quantum nodes appear under More from the community, ready to install in one click. Installing an unverified community node meant going into Settings, typing the npm package name by hand, and accepting a warning that you were about to run unverified code from a public source. A verified node skips all of that. You open the nodes panel on the canvas, type what you are looking for, and if there is a match it shows up under a More from the community section. One click installs it for the whole instance, and from then on it behaves like any built in node: search, drag, drop, connect. This works in both worlds. On n8n Cloud the node is there as soon as verified community nodes are enabled in the Admin Panel. On a self hosted instance the same one click flow works once you turn the feature on and run a recent enough version of n8n, 1.94 or later. To be precise about it, the node does still install, it is not magically already present, but you never leave the editor and you never touch npm. Only an instance owner can install it. After that, everyone on the instance can use it. ## A quantum job is already a pipeline It is fair to ask why a quantum computer needs to live next to your email nodes and your spreadsheets. The answer is that running a quantum job already looks like an automation pipeline, even when you do every step by hand. You prepare a circuit, you submit it to a backend, you wait, you check whether it succeeded, and then you do something with the counts that come back. Submit, wait, branch on success or failure, act on the result. That is a workflow. n8n is an engine built for exactly that shape of work, which is why a trigger that fires when a job finishes and an error trigger that fires when one fails are not gimmicks. They are the natural joints of the thing. I am not going to oversell it. We are in the NISQ era, the hardware is noisy, and nobody is running production workloads on a quantum backend today. But the plumbing around those jobs is real, and it is the kind of work that gets ignored because it is not glamorous. Quantum computing and automation engineering is a narrow strip of ground with very few people standing on it. This node is me planting a small flag there. ## Unofficial is doing real work in that name Every node in the package carries the word Unofficial, and that is deliberate. It is an independent, community built integration. It is not made by IBM, not endorsed by IBM, and not affiliated with IBM in any way. Verified means that n8n reviewed the node and let it into their trusted set, which is a statement about the node meeting their standards. It is not a statement from IBM about anything. I would rather be plain about that than borrow authority I have not earned. ## Where it lives, if you want to try it The package is published on npm as n8n-nodes-ibm-quantum. The source is open on GitHub, and the listing on my n8n Creator profile is public. If you run n8n, the fastest path is the one above: search the canvas, install, connect an IBM Quantum Platform credential, and you are building. - **npm:** [n8n-nodes-ibm-quantum](https://www.npmjs.com/package/n8n-nodes-ibm-quantum) - **GitHub:** - **n8n Creator profile:** > Until this node, this blog kept quantum and automation in separate rooms. This is the door between them. ### How I use AI as a tutor without letting it lie to me URL: https://tuguidragos.com/how-i-use-ai-as-a-tutor-without-letting-it-lie-to-me/ Last updated: 2026-10-03T13:34:17.000Z I work with AI all day. It is woven through the automations I build. It drafts, it sorts, it routes, it does the unglamorous middle of a hundred workflows. I trust it with things that run in production. So when I started learning quantum, reaching for it as a tutor was the most natural move in the world. It is the best study partner I have ever had. It is also the most confident liar I have ever met. Both of those are true at once, and the entire skill is holding them together. Used with a little discipline, an AI tutor is an unfair advantage that students five years ago simply did not have. Used as an oracle, it will quietly hand you a head full of things that are not true, and a warm feeling that you understand them. Here is the discipline. It is not complicated. It is just not optional. ## The two ways it fails There are exactly two failure modes I watch for, and they are sneaky because neither of them feels like a failure. The first is that a clear explanation feels like understanding. It is not. You read something fluent and well structured, you nod, and your brain files away the feeling of having learned, without the substance of it. The explanation was real. Your understanding is borrowed. The second is that it will state false things, about the very topics you are least able to catch, with total composure. AI is at its worst exactly where you need it most. On arithmetic. On matrices. On whether a particular line of code exists in the version you are running. It does not hedge when it is wrong. It is just as smooth as when it is right. That is the dangerous part. Neither of these means stop using it. They mean use it like a tutor whose explanations you love and whose claims you check. ## Trap one: a clear explanation is not understanding This one cost me before I caught it. I would ask for an explanation of, say, [why a Hadamard applied twice returns you to where you started](https://tuguidragos.com/a-qubit-is-not-a-coin-under-a-cup/). I would get something lovely. Clean, intuitive, the right shape. I would feel the click. And then a day later I could not reproduce it. The click was the feeling of a good explanation, not the fact of my own understanding. The fix is a single move, and it is the most valuable habit I have built this whole time. After any explanation, close it and rebuild the idea from memory, in your own words. The moment you try, you find out whether you actually have it, because wherever you stall is a real gap the fluent read quietly hid. I lean on this so heavily across everything I learn that it earns a full post of its own later. Here the use is narrow and specific to the tool. It feels worse than rereading, and that is the point. The effort of dragging an idea out of your own head is what makes it stick, while the comfortable reread is nearly useless. So I treat every AI explanation as step one of two. The explanation, then the closed tab and the blank page. If I cannot rebuild it, I did not learn it, no matter how good the explanation was. ## Trap two: it will hand you dead code with a straight face Now the one that is specific to a field like quantum, where the tools move fast. Qiskit, the main quantum library, changed a great deal across its versions. Code that was correct two years ago is now removed. And AI models, trained on years of old examples, will confidently write you the old way. Ask for a simple circuit and there is a good chance you get something built around a function called execute, which used to be the standard way to run things. ```python from qiskit import execute # AI writes this constantly job = execute(qc, backend, shots=1024) ``` Run that on a current Qiskit and it does not warn you. It does not run a little slower. It dies on the first line: ``` ImportError: cannot import name 'execute' from 'qiskit' ``` That function was removed. The AI does not know, because nothing told it, and it will often defend the code if you push back. The modern way uses a different object, a primitive called a sampler, and if I had trusted the model instead of running its code I would have spent an evening confused about why a supposedly correct example refused to start. The version trap is only the loudest case. The same confidence shows up on plain arithmetic and on matrices, where it is harder to catch because there is no error message, just a wrong number that looks plausible. Ask it to multiply two matrices by hand and it will often produce something that is the right shape, mostly right, and quietly wrong in one entry. Ask it for a gate and you might get a real gate with a sign flipped. None of this comes with a warning label. It reads exactly like the correct answer, which is the entire problem. A wrong import at least crashes. A wrong sign just sits there and corrupts everything you build on top of it. So the rule for anything technical is blunt. Run it. The interpreter is the honest one in the room. If the code runs and gives the answer you expected, good. If it does not, you just caught the lie in seconds, for free, and you go check the official documentation for the real way to do it. Never let an AI be your single source of technical truth. Math, you confirm by hand or in NumPy. Code, you confirm by running it and reading the docs. The AI proposes. The machine disposes. ## The prompts that actually teach Most people use AI like a vending machine. Insert question, receive answer, walk away having learned nothing. The answer was the least valuable thing you could have asked for. Here are the five ways I prompt instead, each one designed to leave understanding behind rather than just a result. I will use quantum examples, but every one of these works for anything hard. **Explain, then quiz me.** Not just "explain superposition." Instead: "Explain superposition to an absolute beginner, then ask me three questions to check whether I actually got it." Now the tool builds the explanation and immediately turns it into a test. You find your gaps in the same breath you fill them. The questions are the valuable half. A good one will not just ask you to repeat the definition, it will ask you to apply it to a case you have not seen, and that is exactly where a shaky understanding falls over. If I breeze through the recall but stumble on the third applied question, I have learned something precise about what I do not yet have, and I can aim my next round straight at it instead of vaguely rereading the whole topic. **Find my mistake.** This is worth ten times more than asking for the answer. "Here is my reasoning for why this gate is unitary, and here is my NumPy code. Where am I wrong?" You did the work, you exposed your thinking, and the AI does the one thing it is genuinely excellent at, spotting the flaw in something concrete that is sitting in front of it. You keep the muscle. It just corrects your form. **Make me a graded set.** "Give me five exercises on the tensor product, increasing in difficulty, then check my solutions one by one." This turns a passive topic into active reps, and reps are where the learning lives. The grading at the end closes the loop so you are not quietly practising the same mistake five times. **Three different analogies.** "Give me three different analogies for relative phase." One analogy can mislead you. Three, side by side, triangulate the real idea, and the places where the analogies disagree are usually where the truth actually lives. This one is pure intuition fuel. **Let me teach it.** "I am going to explain entanglement to you. Correct me wherever I am wrong." This is the Feynman technique with a patient, tireless partner. The act of teaching forces you to find the order, the words, the joints of the idea. Where you fumble, you have found a gap, and you have a tutor right there to patch it. This is the single highest return prompt I use. Here is what that looks like in practice, lightly cleaned up from a real session. I told it: "A Hadamard puts a qubit into superposition, so applying it twice puts it into even more superposition." That feels reasonable. It is also wrong, and the tutor caught it cleanly. Two Hadamards in a row cancel out and return the qubit to where it started, because the gate undoes itself. I had a tidy, confident, false belief, and saying it out loud to something that could correct me dragged it into the light in one move. I would never have caught it by rereading, because rereading the correct explanation does not surface my incorrect version of it. Only producing my version does. That is the whole power of teaching it back. Notice what every one of these has in common. The work stays with you. The AI is a mirror, a sparring partner, an examiner. It is never the thing doing the understanding on your behalf. ## What I never ask it to do The flip side of the good prompts is a short list of things I refuse to hand over, because handing them over is how the tool quietly hollows out your learning. I never ask it to do my recall for me. The point of closing the tab and rebuilding an idea from memory is that the effort is mine. If I get stuck and immediately ask the AI to finish the thought, I have skipped the exact rep that would have built the understanding. The stall is the workout. Outsourcing it defeats the purpose. I never ask it whether I understand something. It cannot know, and it is far too agreeable to be trusted with the question. Ask "do I get this?" and you will usually get encouragement, because that is the path of least resistance for a model trained to be helpful. The only honest test of whether I understand something is whether I can produce it or use it myself. That is a test I run, not a verdict I request. And I never paste its unverified math or code straight into something I am building on. Anything technical gets run or checked first. A proposed answer is a draft, not a fact, and I treat the gap between those two as sacred. The moment I forget it, the tool stops being a tutor and becomes a very persuasive source of bugs. ## The one rule under all of it If you strip everything above down to a sentence, it is this. The AI is a tutor, not an authority. A good tutor explains things well, asks you hard questions, catches your mistakes, and never minds repeating themselves. You still do the learning. You still check the facts that matter, because a tutor can be wrong, and this particular tutor is wrong with a remarkably steady voice. The instant you promote it from tutor to oracle, you have handed your understanding to something that cannot tell you when it is making things up. The trust is earned by the verification, not assumed in spite of its absence. That is not cynicism. That is just how you work safely with a powerful tool that does not know its own limits. ## One honest caveat An AI tutor is a tool, and a tool is not company. The hardest part of learning something difficult on your own is not the difficulty, it is the isolation. A model on call at midnight helps enormously with the difficulty and not at all with the isolation. So I use it for what it is good at, and I still go find real people, the forums, the communities, the places where humans who actually build this stuff hang out. The AI accelerates the learning. It does not replace the company. Do not let it quietly convince you that it is enough on its own. ## Why this bites harder in quantum Everything above is true for learning anything. It is just sharper here, for two reasons worth naming. The first is that quantum is genuinely unintuitive. With a popular web framework, a wrong mental model gets corrected fast, because the thing visibly does not work and a thousand tutorials nudge you back on track. Quantum has fewer of those guardrails, and the subject actively fights your intuition. So a confident wrong explanation does more damage, because you have less of your own instinct to flag it, and the illusion of understanding can ride along unchallenged for weeks. The recall habit is not a nicety here. It is the main thing standing between you and a tidy collection of misconceptions. The second is that the material online is thinner and changes faster. There is far more correct, recent, well ranked content about building a website than about running a circuit on this year's Qiskit. That means more of what the model absorbed is old or sparse, which is exactly the soil hallucinations grow in. The version trap from earlier is not a fluke. It is the predictable result of a fast moving, lightly documented field meeting a model trained mostly on the past. Put those together and the conclusion is not use AI less. It is verify more, here, than you would anywhere else. The upside of a tutor is even bigger in a hard, sparse field, because there is less else to lean on. So is the cost of trusting it blindly. The discipline scales with both. ## The thing behind the thing I spend my days putting AI inside systems and making those systems reliable. The lesson there is the same one as here. A powerful component is only as good as the checks around it. You do not refuse to use it because it can fail. You build it in, and you build the verification in right alongside it, and the whole thing becomes trustworthy precisely because you never trusted any single piece blindly. My quantum tutor never sleeps, never sighs, and never runs out of patience for my fifth version of the same question. It is also wrong sometimes, with a perfectly straight face. Both true. I get the entire upside of the first by never forgetting the second. The tab closes, the page stays blank until I can fill it myself, and the code has to run. That is the whole trick. ### Running quantum circuits on real IBM hardware from n8n URL: https://tuguidragos.com/quantum-circuits-ibm-hardware-n8n/ Last updated: 2026-09-13T20:59:35.000Z Over the past week I built a small open source tool that runs a quantum circuit on real IBM hardware as a step inside an n8n workflow. You write a circuit, an automation picks a quantum processor, the job runs on real qubits, and the measurement counts flow back into the rest of your pipeline. This post is the full account. What the result actually was, how the node is built, why I reached for n8n instead of Make or Zapier, the exact loop from end to end, and the one step that quietly breaks most first attempts. I have kept it honest. Where a shortcut is more fragile than it looks, I flag it. ## The result came back from a real 156 qubit processor I ran the smallest interesting quantum circuit, a Bell state. Two qubits, one Hadamard, one controlled NOT, then a measurement of both. In theory it produces a perfectly correlated pair: every shot should read either 00 or 11, and never 01 or 10\. On paper the state is (|00⟩ + |11⟩)/√2. The job ran on ibm\_kingston, a Heron r2 processor with 156 qubits, for 1024 shots. The counts came back like this: - **00**: 493 - **11**: 497 - **01**: 29 - **10**: 5 So 990 of 1024 shots, about 97 percent, landed on the correlated 00 and 11 outcomes. The remaining 3 percent landed on 01 and 10, which the ideal circuit should never produce. That 3 percent is the interesting part. It is not a bug in the node or in the circuit. It is the hardware showing through. A real quantum processor has readout error, gate error and a little decoherence, and on a near-term device that error floor is exactly what leaks into the forbidden outcomes. An ideal simulator would have returned a clean split with nothing in 01 or 10\. Be careful with how far that gets you, though. A noisy simulator can reproduce a distribution like this one, so the counts on their own do not prove which machine ran the job. What proves it is the job record: the backend name and the job ID that IBM returns with the result, which you can open in the console afterwards. The error floor is consistent with hardware, and the job record is the receipt. ![Bell state measurement counts from ibm_kingston shown in n8n, with 493 on 00 and 497 on 11 out of 1024 shots](https://tuguidragos.com/content/images/2026/06/n8n-ibm-quantum-bell-state-result.png) The counts from the real run on ibm\_kingston: 493 on 00 and 497 on 11, with the rest as hardware noise. ## The node is a thin wrapper, not a Qiskit bundle When I went looking, there was no n8n node for IBM Quantum, so I wrote one. It does not contain Qiskit. It does not contain any quantum library at all. It is a thin wrapper over the IBM Quantum Platform REST API, with zero runtime dependencies. That constraint was forced and then turned out to be a feature. Verified n8n community nodes are not allowed to ship runtime dependencies, so bundling Qiskit was off the table from the start. Instead the node speaks to Qiskit Runtime directly over HTTP. It exchanges your IBM Cloud API key for a short-lived token, sends the circuit as an OpenQASM 3 string, polls the job, and parses the result. Circuits go in as plain text, counts come out as plain JSON. Inside n8n it shows up as one node with five resources and around twenty actions: - **Backend**: list devices, read configuration, properties and status, and pick the least busy one. - **Circuit**: build an OpenQASM 3 program from a list of gates, or import one you already have. - **Job**: submit to the Sampler, which returns counts, or the Estimator, which returns an expectation value, check status, get results, list, cancel and delete. - **Session**: open a batch or dedicated session for hybrid loops, then close it. - **Account**: read your usage and your instance allocation. It also ships two triggers, one that fires when a job finishes and one that fires only when a job fails or is cancelled. ![The IBM Quantum node in n8n next to a published workflow, where a webhook feeds Get Least Busy and Submit to Sampler, with the node action list open showing twenty actions](https://tuguidragos.com/content/images/2026/06/n8n-ibm-quantum-node-actions.png) The node in n8n, next to a published workflow that uses it. It labels itself Unofficial, since it is community built and not affiliated with IBM, and it ships around twenty actions. ## Automation is the point, and quantum is now just an API Here is the shift that made this worth building. Access to quantum hardware today is an API. Qiskit Runtime is a REST service underneath the Python SDK. The moment something is an API, it can be a step in a workflow, and the moment it is a step in a workflow, all the ordinary automation patterns apply to it. That is what the node unlocks. You can submit a circuit on a schedule or in response to a webhook. You can let an automation choose the least busy processor instead of hard-coding one. You can fan the results into a database, a notification, a spreadsheet or a dashboard. You can retry on failure, or fall back to a simulator. You can run a hybrid loop that submits, reads, adjusts and submits again. The quantum computer stops being a special place you visit inside a notebook and becomes one node among many in a pipeline. I want to be precise about what this is and is not. This is not quantum advantage, and it is not a production quantum workload. It is plumbing. The interesting thing is only that the plumbing now reaches real qubits, and that you can wire it up by dragging boxes around. ## Why I chose n8n over Make and Zapier People ask why n8n and not Make or Zapier, so here is the honest reasoning for this specific project. The three tools are not really built for the same job, so it helps to say what each one is before saying why I picked one. **Zapier** is the most locked-down of the three and the friendliest to beginners. It is cloud only, it has only a limited code step, and it prices per task, so a long-running poll gets expensive quickly. It is built to connect ready-made apps with as little friction as possible. **Make** is the strongest visual builder. It handles branching and data shaping well and has a large catalogue of connectors, but it is also cloud only, it prices per operation, and its custom apps live inside Make and are defined through its own builder rather than published as open packages. **n8n** is fair-code, self-hostable, and friendly to real code. It exposes proper expressions and a code node, and its community nodes are open npm packages written in TypeScript. For this project, that last difference decided everything. Here is what it meant in practice. **I could own the integration.** An n8n community node is an npm package that I published and that anyone can read, install and verify, and n8n runs a verified community node registry where an independent node can be discovered. Neither Zapier nor Make lets an outside developer publish an open, inspectable integration as a standalone package. The entire point of this project was a public, timestamped artifact, and only n8n let the integration itself be that artifact. **I could self-host it.** n8n runs on my own machine, so I owned the runtime from end to end. Zapier and Make are cloud only, which matters more than usual when you are wiring up an API with real credentials and jobs that touch real hardware. **The cost model fit long jobs.** A hardware job can sit in a queue for minutes or hours. Zapier charges per task and Make charges per operation, so polling a job to completion burns paid actions either way. A self-hosted n8n has no per-action cost, so a trigger that quietly polls every minute until a job finishes is free. **I could drop into code where I needed it.** Turning the hex samples from the device into a counts histogram is a few lines of code in n8n. On Zapier that kind of work is awkward, and on Make it is a fight with a visual builder. To be fair to the other two, this is not a verdict that n8n is better in general. If you are not technical and you want to connect Gmail to a spreadsheet in five minutes, Zapier is the right tool. If you want a polished visual builder with strong data handling and a large connector catalogue, Make is excellent. The choice here was driven by this being a developer-first, self-hosted, open source project, and for that shape of work n8n fit and the other two did not. ## The full loop, from VS Code to real qubits Here is the path the way I actually run it. First, I build and transpile the circuit in VS Code with Qiskit, then send it to an n8n webhook. Note one thing here. The script transpiles the circuit for a specific processor, but it does not get to decide where the job runs. The script is small and carries no credentials, since the API key lives inside the n8n credential and not in the code: ```python import requests from qiskit import QuantumCircuit, qasm3 from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit_ibm_runtime.fake_provider import FakeFez N8N_WEBHOOK = "http://localhost:5678/webhook/quantum-submit" BACKEND = "ibm_fez" SHOTS = 1024 def build_circuit() -> QuantumCircuit: qc = QuantumCircuit(2, 2) qc.h(0) qc.cx(0, 1) qc.measure([0, 1], [0, 1]) return qc def to_isa_qasm(qc: QuantumCircuit) -> str: backend = FakeFez() isa = generate_preset_pass_manager(optimization_level=1, backend=backend).run(qc) return qasm3.dumps(isa) qc = build_circuit() qasm = to_isa_qasm(qc) resp = requests.post(N8N_WEBHOOK, json={"qasm3": qasm, "backend": BACKEND, "shots": SHOTS}, timeout=320) print(resp.json()) ``` So the script picks a backend for transpilation only. Choosing where to actually run is the workflow's job. Second, the webhook hands the circuit to the workflow, and this is the part I want to highlight. I do not hard-code a machine. The Get Least Busy node looks at every processor available on my plan, ranks them by queue length, and returns the freest one. On this run that was ibm\_kingston, sitting at an empty queue: ![Get Least Busy node in n8n returning ibm_kingston as the freest IBM quantum processor, ahead of ibm_marrakesh and ibm_fez](https://tuguidragos.com/content/images/2026/06/n8n-ibm-quantum-least-busy-backend.png) Get Least Busy ranks the online processors by queue length and returns the freest one available on the plan. Third, Submit to Sampler sends the circuit to whichever backend Get Least Busy returned, not the one named in the script, and gets back a job id immediately, without blocking. This works because every processor on my plan is the same Heron family, so a circuit transpiled for one is valid on another, a point I come back to below: ![The Submit to Sampler node in n8n sending an OpenQASM 3 circuit to ibm_kingston and returning a job id](https://tuguidragos.com/content/images/2026/06/n8n-ibm-quantum-submit-to-sampler.png) Submit to Sampler uses the backend chosen by Get Least Busy and returns a job id straight away, so nothing waits here. Fourth, a later step waits for the job and reads the counts once it is done. For a short job you can poll inline. For a long hardware queue the healthier pattern is to let the submit finish on its own and use the trigger that fires when the job completes, so nothing holds an execution open for an hour. That is the loop. Qiskit on one side, real qubits on the other, n8n in the middle. ## Transpilation is the step everyone misses This is the part that quietly breaks most first attempts, and it is worth understanding because it is not obvious. A textbook circuit uses high-level gates like h, the Hadamard, and cx, the controlled NOT. A real quantum chip does not run those directly. Each processor executes only a small set of native gates. A Heron processor like ibm\_kingston, for instance, runs rz, sx, x and cz, plus measure and reset, and its qubits are wired in a fixed layout. Translating a circuit into a device's native gates and connectivity is called transpilation, and the result is an ISA circuit, where ISA stands for instruction set architecture. The Qiskit Runtime REST API does not transpile for you. It expects an ISA circuit and rejects anything else. Submit a raw Hadamard to real hardware and the job fails with reason code 1517 and a message like this: ``` The instruction h on qubits (0,) is not supported by the target system. Transpile your circuits for the target before submitting a primitive query. ``` This is not a node bug. The node builds, submits and reads the job correctly. The hardware simply refuses a circuit that is not in its own gate set. The fix is to transpile locally with Qiskit before sending. You do not even need live credentials for this, because a fake backend carries the real topology and native gate set: ```python from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit_ibm_runtime.fake_provider import FakeFez backend = FakeFez() isa = generate_preset_pass_manager(optimization_level=1, backend=backend).run(qc) ``` A Bell circuit transpiled for a Heron device comes out in native gates only. The abstract Hadamard and controlled NOT are gone, replaced by rz and sx rotations and a cz: ``` OPENQASM 3.0; include "stdgates.inc"; bit[2] c; rz(pi/2) $0; sx $0; rz(pi/2) $0; rz(pi/2) $1; sx $1; rz(pi/2) $1; cz $0, $1; rz(pi/2) $1; sx $1; rz(pi/2) $1; c[0] = measure $0; c[1] = measure $1; ``` One detail worth stating plainly, since it follows directly from letting the workflow pick the backend. The script transpiles for one Heron processor, and the Get Least Busy node can then choose a different one. That is safe here because every processor on my plan is a Heron r2 and they share the same native gate set and the same layout of connected qubits, so an ISA circuit built for one is valid on another. It is not safe in general. If the freest device ever came from a different family with a different layout, the hardware would reject the circuit, so for anything beyond a same-family pool you should pin the backend you transpiled for. ## What it does not do, and the honest caveats A few things I want on the record, since calibrated is better than impressive. **This is near-term hardware, so noise is normal.** The 3 percent of shots in the wrong outcomes is the device, not a defect. Expect error on any current quantum processor. **Transpilation is on you, not on the node.** The node does not transpile. You transpile with Qiskit for one device, feed the node that ISA circuit, and pin the backend to that exact device, or at least to a device of the same family and layout, because an ISA circuit is specific to one topology. **Long jobs need the right pattern.** A job that blocks an execution for an hour is fragile. Submit, then react to the completion trigger, rather than holding one run open. **The free plan has limits.** The Open plan has a monthly time budget and cannot use dedicated sessions. The node exposes usage so you can watch the budget, but it cannot give you more of it. ## How to try it The package is on npm as n8n-nodes-ibm-quantum. On a self-hosted n8n you install it from the community nodes screen by entering that name. You will need an IBM Cloud account with access to the IBM Quantum Platform, an API key, and the CRN, the Cloud Resource Name, of your Qiskit Runtime instance. The credential in n8n has a test button that calls the backends endpoint, so you can confirm all of it at once before you run anything. ![The IBM Quantum credential in n8n showing a successful connection test](https://tuguidragos.com/content/images/2026/06/n8n-ibm-quantum-credential-test.png) The credential test calls the backends endpoint, so the API key, instance and region are all verified in one click. ## Why I built it The reason I built this sits at an intersection I find under-explored: quantum computing and automation engineering. Most quantum tooling assumes you live in a notebook, and most automation tooling has never needed to reach a quantum processor. Putting the two together is a small thing today, a Bell state and some plumbing, but the plumbing is real and the qubits are real. That is the part I wanted to make concrete rather than talk about. The node is open source, the result is reproducible, and the whole thing is dated in public on npm and in git. If you want to read the code or run the circuit yourself, the links are at the end. ## Questions people ask **Can you run a quantum circuit from n8n?** Yes. With this node you build or import an OpenQASM 3 circuit, submit it to the IBM Quantum Platform, and read the measurement counts, all as n8n steps. The circuit in this post ran on a real 156 qubit processor. **Does the node bundle Qiskit?** No. It is a thin wrapper over the Qiskit Runtime REST API with zero runtime dependencies. You can still use Qiskit on your own machine to build and transpile circuits, then hand the result to the node. **Is it affiliated with IBM?** No. It is an unofficial, community-built node. IBM Quantum and Qiskit are trademarks of IBM. The node even labels itself Unofficial inside n8n. **n8n, Make or Zapier for this kind of thing?** For an open, self-hosted, code-first integration that you publish and own, n8n fit. Zapier is the better choice for quick, non-technical connections between ready-made apps, and Make is the better choice for polished visual workflows with strong data handling. They solve different problems. **Why did the circuit run on a different processor than the one I transpiled for?** Because the workflow picks the backend, not the script. The Get Least Busy node returned the freest processor on the plan, and it happened to differ from the transpile target. That is fine here because both are Heron r2 devices with the same native gate set. For different hardware families, pin the device you transpiled for. ## Links - The code on GitHub: - The package on npm: - The video demonstration: ### The density matrix: what the state vector misses URL: https://tuguidragos.com/the-density-matrix-what-the-state-vector-misses/ Last updated: 2026-09-13T18:42:19.000Z For most of this series, a quantum state has been a vector. A short list of amplitudes, one slot per outcome, that I could write down and reason about by hand. The state |0⟩, the superposition (|0⟩ + |1⟩)/√2, a Bell state across two qubits, the standard entangled pair. That picture is correct, and it carries you a long way. It also runs out of room, and this badge is about the language that picks up where it stops. The badge is called General Formulation of Quantum Information. The word that matters is general. A state vector can only describe a system that is pure and on its own. Real qubits are rarely either. They sit inside noise. They are entangled with things you do not control and cannot measure. For those situations you need a bigger object, and that object is the density matrix. ## The state vector runs out of room Here is the gap, concretely. A state vector says: I know exactly what state this qubit is in, even when that state is a superposition. There is no uncertainty about the state itself, only about what a measurement will return. But suppose I hand you one qubit of an entangled pair and keep the other one myself, somewhere you cannot reach. What state is your qubit in? There is no single vector that answers this. Your qubit does not have a state of its own in the way the vector formalism requires. It only has a state jointly, with mine. The vector language has no slot for "half of an entangled pair," and that is not a small edge case. It is most of real quantum computing, where every qubit is leaking a little of itself into its surroundings whether you like it or not. ## What a density matrix is A density matrix, written ρ, is the more general way to say what state a system is in. For a pure state |ψ⟩ it is just ρ = |ψ⟩⟨ψ|, the outer product, which carries exactly the same information as the vector. Nothing new yet. The new part is that a density matrix can also describe a mixture: not a superposition, but honest classical uncertainty about which of several states you actually have. [A coin that is either |0⟩ or |1⟩](https://tuguidragos.com/a-qubit-is-not-a-coin-under-a-cup/), fifty fifty, with no quantum coherence, no definite relative phase, between the two, has a density matrix as well, and it is a different object from the superposition (|0⟩ + |1⟩)/√2 even though both give fifty fifty when measured. One number tells these apart, and I leaned on it constantly while learning this material. It is the purity, Tr(ρ²), the trace being the sum of the diagonal. For a pure state it equals exactly 1\. For anything mixed it is less than 1, bottoming out at 0.5 for a single qubit that is maximally mixed, a true coin flip with no information left. Purity is the dial that reads how much quantum certainty a state still holds, and watching it move is how the abstract idea of mixedness became concrete for me. ## The result that made it click I will show you the one calculation that turned this from a definition into an intuition. Take a Bell state, a maximally entangled pair of two qubits. As a whole it is pure. Its purity is exactly 1, no uncertainty at all. Now throw away one of the two qubits and ask what is left. ```python import numpy as np from qiskit.quantum_info import Statevector, DensityMatrix, partial_trace, entropy, purity bell = Statevector([1, 0, 0, 1]) / np.sqrt(2) # (|00> + |11>) / sqrt(2) rho = DensityMatrix(bell) print(round(float(np.real(purity(rho))), 6)) # 1.0 -> the pair is perfectly pure reduced = partial_trace(rho, [1]) # ignore qubit 1, keep qubit 0 print(np.round(np.real(reduced.data), 6)) # [[0.5 0. ] [0. 0.5]] print(round(float(np.real(purity(reduced))), 6)) # 0.5 -> maximally mixed print(round(float(entropy(reduced, base=2)), 6)) # 1.0 -> one full bit of uncertainty ``` Run it. The pair has purity 1\. The single qubit you kept has purity 0.5 and one full bit of entropy. Its density matrix is the identity divided by two, which is the mathematical way of writing "a coin I know nothing about." Sit with that, because it is the whole lesson. A perfectly known, perfectly pure two qubit state, looked at one qubit at a time, becomes maximal ignorance. The information did not vanish. It was never stored in either qubit alone. It lives entirely in the correlation between them, and the moment you ignore the partner the correlation is gone and you are left holding noise. Maximal entanglement, viewed locally, is indistinguishable from knowing nothing at all. Weaker entanglement leaves the single qubit somewhere in between, mixed but not completely so, and the purity is exactly the dial that tells you where. The state vector cannot express that sentence. The density matrix says it in one line: ρ = I/2. ## Channels: how a state degrades The second piece of the badge is about how states change when the world interferes, and the general tool for that is a quantum channel. Where a gate is a clean, reversible rotation, a channel is the honest model of evolution that includes noise, and it can take a pure state and leave it mixed. The standard worked example is the depolarising channel, which with some probability throws your state away and replaces it with pure noise. Watch what it does to the purity of a qubit that started perfectly pure. ```python import numpy as np from qiskit.quantum_info import DensityMatrix, Statevector zero = DensityMatrix(Statevector([1, 0])) # pure |0>, purity 1.0 I2 = np.eye(2) / 2 for p in [0.0, 0.2, 0.5, 1.0]: out = (1 - p) * zero.data + p * I2 # depolarising with strength p print(p, round(float(np.real(np.trace(out @ out))), 3)) # 0.0 -> 1.0 0.2 -> 0.82 0.5 -> 0.625 1.0 -> 0.5 ``` Run it. As the noise strength climbs from nothing to total, the purity slides from 1.0 down to 0.5, from a perfectly known state to a coin flip. This is not an abstraction. It is exactly what decoherence, the qubit leaking into its surroundings, does in physical terms, now written in the language that can actually carry it. A real qubit on real hardware is a pure state being slowly eaten by channels like this one, and the density matrix is how you track the damage as it happens. You cannot tell that story with state vectors, because a state vector cannot represent a half ruined state. A density matrix can, and that is the entire reason this formalism exists. ## General measurements, briefly The third piece generalises measurement itself. The projective measurements I had used until now, read the qubit in the |0⟩, |1⟩ basis, are a special case. The general object is called a POVM, a set of operators that describe any physically allowed measurement, including soft ones that only partially disturb the state and ones that ask questions the plain basis cannot. I am not going to pretend I have deep fluency here yet. What I have is the shape: measurement, like evolution and like the state itself, has a general form that contains the simple version I already knew as one corner of a larger space. ## What this badge says, and what it does not [This is an advanced badge](https://www.credly.com/badges/70862ebb-16e4-4a4b-b1d3-abfb7102727b/public%5Furl), General Formulation of Quantum Information, and I want to be precise about what that does and does not mean, because the point of writing in public is to not oversell. The badge certifies that I worked through IBM's coursework on the general formulation, density matrices, channels, and generalised measurements, and that I can reason with these objects: read a density matrix, compute a purity, take a partial trace, recognise a channel. The small calculations above are mine, run on my own machine, and you can run them yourself and get the same numbers. What it does not certify is research, novelty, or fluency on hardware. It is coursework, attesting understanding, not a claim that I have done anything new with these tools or run them on a real device. That distinction is the whole brand here. A badge is honest evidence that I learned a body of material carefully. It is not a costume. I would rather you trust the small claim completely than half believe a large one. ## Where I am learning it Free. The whole thing came from IBM Quantum Learning, which gives the courses, the interactive labs, and the verifiable Credly badges at no cost under the open plan, taught by the people who build Qiskit. The general formulation course is the source for everything above, and the best teacher was doing the labs rather than only reading them. Working through density matrices and channels by hand, then checking each one in Qiskit until the numbers agreed, is what moved this from notation I could parse to a language I can think in. ## The pattern under all three Looking back at the badge as a whole, it is one idea repeated three times. The state, the evolution, and the measurement each have a simple version I already knew, the vector, the gate, the basis readout, and each turns out to be the clean special case of something more general, the density matrix, the channel, the POVM. The general versions are the ones that survive contact with a real, noisy, entangled world. Learning them did not replace what I knew. It put a frame around it and showed me where the edges were. > The state vector told me what a qubit is when it is alone and perfect. The density matrix told me what it is the rest of the time, which is to say, almost always. ### Three quantum ways to diagonalise a giant matrix URL: https://tuguidragos.com/three-quantum-ways-to-diagonalize-a-giant-matrix/ Last updated: 2026-09-13T18:42:17.000Z A surprising number of the hard problems people actually want a quantum computer for are the same problem underneath. Find the lowest eigenvalue of a very large matrix. The energy of a molecule in its ground state, the behaviour of a new material, certain optimisation problems, all of them reduce to one task: build a large Hermitian matrix, one equal to its own conjugate transpose, called a Hamiltonian, then find its smallest eigenvalue and the vector that produces it. The badge behind this post, IBM's Quantum Diagonalization Algorithms, is about the quantum algorithms built for exactly that, and there are three families worth telling apart. ## One problem wearing many costumes The matrix in question is a Hamiltonian, the object that encodes a system's energy. Its eigenvalues are the allowed energies, and the smallest one is the ground state energy, the lowest the system can sit at. For chemistry that ground state energy is the prize, because it decides how molecules bond and react. The whole game, across chemistry and materials and more, is reading one number off a matrix: the bottom eigenvalue. Diagonalising a matrix means finding its eigenvalues, and for small matrices that is a solved, boring problem. The trouble is the size. ## Why you cannot just diagonalise it The matrix for n qubits is 2ⁿ by 2ⁿ. That exponential is what makes this hard. For 10 qubits the matrix is 1024 by 1024, which a laptop diagonalises instantly. For 50 qubits it is over a quadrillion by a quadrillion, and there is not enough memory on Earth to even write it down, let alone diagonalise it. Classical exact diagonalisation, the function I am about to use on a toy, simply stops being possible somewhere around twenty qubits. Everything in this badge is a strategy for getting the bottom eigenvalue of a matrix you can never hold in memory. ## The honest small example To make the rest concrete I need a matrix small enough to diagonalise the old fashioned way, so I have an answer key to check the clever methods against. Here is a real one, two qubits, a pair of coupled spins. ```python import numpy as np X = np.array([[0, 1], [1, 0]], dtype=complex) Y = np.array([[0, -1j], [1j, 0]], dtype=complex) Z = np.array([[1, 0], [0, -1]], dtype=complex) H = np.kron(X, X) + np.kron(Y, Y) + np.kron(Z, Z) # 4x4 Hamiltonian evals = np.linalg.eigh(H)[0] print(np.round(evals, 3)) # [-3. 1. 1. 1.] ``` Run it. The eigenvalues are -3, 1, 1, 1, so the ground state energy is -3\. That is the number every method below is trying to recover without the luxury of building H and calling eigh on it. And there is a detail I did not expect the first time I saw it. The ground state, the vector with energy -3, is the singlet, (|01⟩ - |10⟩)/√2, which is an entangled state. The lowest energy configuration of two coupled spins is not either spin pointing in some definite direction. It is the two of them entangled. Entanglement, which had only come up in passing so far, turns out to be where the energy is minimised, which is a quiet and satisfying link between two things I had learned as if they were separate. ## Variational: guess, measure, adjust The first family is the variational one, and it rests on a single guarantee from physics: the average energy of any state you can prepare is never lower than the true ground energy. So you build a circuit with adjustable knobs, a parameterised state |ψ(θ)⟩, measure its average energy, and let a classical optimiser turn the knobs to push that number down. The lowest you can drive it is your estimate of the ground energy. Here is that loop against the toy Hamiltonian, the same H written as a sum of Pauli terms, the X, Y and Z matrices from the first block, with a small two qubit circuit and an ordinary optimiser, run on a simulator. ```python import numpy as np from qiskit.circuit import QuantumCircuit, Parameter from qiskit.quantum_info import SparsePauliOp, Statevector from scipy.optimize import minimize H = SparsePauliOp(["XX", "YY", "ZZ"], coeffs=[1, 1, 1]) # XX + YY + ZZ a, b, c, d = (Parameter(p) for p in "abcd") ansatz = QuantumCircuit(2) # the adjustable knobs ansatz.ry(a, 0); ansatz.ry(b, 1) ansatz.cx(0, 1) ansatz.ry(c, 0); ansatz.ry(d, 1) def energy(x): # average energy of the prepared state state = Statevector(ansatz.assign_parameters(dict(zip([a, b, c, d], x)))) return float(np.real(state.expectation_value(H))) rng = np.random.default_rng(0) best = min((minimize(energy, rng.uniform(0, 2 * np.pi, 4), method="COBYLA") for _ in range(20)), key=lambda r: r.fun) print("VQE ground energy:", round(best.fun, 3)) # -3.0 ``` Run it. The optimiser drove the energy down to -3.0, landing on the exact ground value. This is VQE, [the variational quantum eigensolver](https://tuguidragos.com/i-earned-an-advanced-ibm-quantum-badge/), and its shape is a parameterised pipeline: define the circuit once, leave the angles open, sweep them. The difference from a plain sweep is that here an optimiser chooses the next angles based on the last measured energy. The quantum computer prepares states and reports their energy. The classical computer decides where to look next. The work is split between the two, and that division is the whole character of this style of algorithm. ## Krylov: build a tiny good subspace The second family is quieter and, to me, more elegant. The full space is impossibly large, but the ground state usually lives in a tiny, well chosen corner of it. Krylov methods build that corner on purpose. Starting from some state |ψ⟩, you generate a small handful of related states by applying the Hamiltonian again and again, |ψ⟩, then H|ψ⟩, then H²|ψ⟩, and so on, which tends to pull the set toward the extreme ends of the spectrum, the lowest energies and the highest ones alike. Then you diagonalise the Hamiltonian only inside that small subspace, a tiny matrix you can handle, and its lowest eigenvalue approximates the true one. On a quantum computer the subspace is built from states evolved under the Hamiltonian for a few short times rather than from powers of H, and the small matrix is filled in by measuring overlaps between them, which is the step a classical machine cannot afford at scale. Here it is on the toy, with a two dimensional subspace built from a start vector and one application of H. ```python import numpy as np from qiskit.quantum_info import SparsePauliOp H = SparsePauliOp(["XX", "YY", "ZZ"], coeffs=[1, 1, 1]).to_matrix() rng = np.random.default_rng(1) v = rng.normal(size=4); v = v / np.linalg.norm(v) # needs nonzero ground state overlap w = H @ v; w = w - np.vdot(v, w) * v; w = w / np.linalg.norm(w) # one step, orthogonalised K = np.column_stack([v, w]) # the 2 dim subspace H_small = K.conj().T @ H @ K # H restricted to it print("Krylov ground energy:", round(float(np.linalg.eigvalsh(H_small)[0]), 3)) # -3.0 ``` Run it. That two by two problem returns -3.0 exactly, the same answer the full matrix gave. Do not read too much into the word exactly, because this toy is easier than it looks. My Hamiltonian has only two distinct eigenvalues, -3 and 1, so a two dimensional Krylov space is already guaranteed to hold the ground state component in full. On a real Hamiltonian with a dense spectrum you get an approximation that tightens as the subspace grows, and that is the honest version of the trade. The trade is the heart of it. You replace one impossible diagonalisation with one easy diagonalisation, on a subspace small enough to be classical, and you accept an approximation that improves as the subspace grows. There is no optimiser searching here. You are constructing a good basis and then solving exactly inside it. The start vector is not free either. Krylov can only reach what the first vector already touches, so a start state with no overlap at all on the ground state leaves the method stuck forever. On this Hamiltonian |00⟩ is exactly that trap: it is a triplet state, one of the three states with energy 1, orthogonal to the singlet, so the subspace collapses to a single vector and the method reports 1 instead of -3\. A random start avoids that with probability one, which is why the code uses one. ## Sample based diagonalisation: let the hardware choose the basis The third family is the newest, and it is the one that fits my own instincts best, because it is a clean division of labour between a quantum machine and a classical one. The idea, called sample based quantum diagonalisation, goes like this. Run a quantum circuit and measure it many times. The bitstrings that come back most often point to the configurations that matter most for the ground state. Collect those few important configurations, build the Hamiltonian restricted to just that small set, and diagonalise that small matrix on a classical computer. Then feed what you learned back in and repeat. The quantum computer is doing the one thing it is good at, exploring an enormous space and reporting which corners are worth looking at. The classical computer is doing the one thing it is good at, exactly diagonalising a small, well chosen matrix. Neither could do the job alone at scale, and the method is built around handing each part to the machine suited for it. As someone whose day work is wiring systems together so each piece does what it does best, this is the algorithm in the badge that felt the most like home. ## What this badge says, and what it does not [The badge, Quantum Diagonalization Algorithms, is an intermediate one](https://www.credly.com/badges/fb2aaaa0-8c5c-430a-ba58-3da12fc89bc7/public%5Furl), and the honest reading of it is this. It certifies that I worked through IBM's coursework on quantum diagonalisation, that I understand the variational, Krylov, and sample based families, and that I can reason about which kind of problem suits which approach. The toy calculations above are mine, run on my own machine, and they are reproducible. The official badge description also says the earner can implement these algorithms on real quantum computers. I want to be careful and exact about that line. The course does cover implementation on real backends, and the small versions above I have run in simulation. My own first run on a real quantum processor is a separate milestone, and it is one I have not published yet. I am not going to let a badge's wording quietly promote a simulator result into a hardware result. The understanding is real and earned. The hardware run is still ahead of me, and when I do it, it gets its own honest post. ## Where I am learning it Free, through IBM Quantum Learning on the open plan, IBM's free tier, with the interactive labs and the verifiable badge at the end. The diagonalisation course is the source for the three families above. As in the earlier posts where I built things, the part that actually taught me was building the toy myself, computing the answer key with plain linear algebra, and then watching each method recover that same number a different way. Three roads to one eigenvalue is a far clearer lesson when you have walked two of them in code on a problem small enough to check by hand. ## Three roads, one number The thing I am taking from this badge is not three algorithms as separate recipes. It is one problem, find the bottom of a matrix too big to build, approached three ways that differ only in how they beat the size. Variational searches with an optimiser. Krylov constructs a small honest subspace. Sample based lets the hardware vote on which corners matter and hands the rest to a classical solver. Different instincts, same target, and knowing which instinct fits a given problem is most of the skill. > The matrix was too big to ever write down. The answer was a single number at the bottom of it. Most of quantum computing, it turns out, is clever ways of reaching that number without ever holding the matrix. ### IBM says I understand quantum machine learning now URL: https://tuguidragos.com/ibm-says-i-understand-quantum-machine-learning-now/ Last updated: 2026-09-13T18:54:36.000Z IBM just told me I understand [quantum machine learning](https://www.credly.com/badges/60c08414-615e-4a5c-b8aa-357c90fb5ba6/public%5Furl). That is what the badge attests to, and a badge attests to understanding, not to a result I produced on real hardware. I want to be precise about that from the first line, because the distinction is the whole point. Concretely, the badge is a course followed by an exam: 20 questions, 80 percent to pass. Machine learning is the work I actually do, the systems I build and reason about every day. Quantum is the harder thing I have been learning in the open. This is the subject where the two meet, and I wanted to know exactly what that meeting looks like before claiming anything about it. So here is what the badge actually puts in front of you, the thing that surprised me, and the part most people skip. ## The badge trains you to say no, not just yes The stated goal is to implement quantum kernel methods and variational classifiers with Qiskit primitives, adjust an example onto your own data, and then judge where quantum machine learning would actually help. That last part is the one I did not expect. The course spends as much effort on identifying where quantum machine learning is not promising as on where it is. That is rare, and it is the most useful thing in the badge. Most material on this subject sells you the upside. This one hands you the limits in the same breath: training time, noise, and the compounding readout error, the chance a qubit is read as 1 when it was 0 or the other way round, you get when a model depends on measuring many states. It teaches the technique and the skepticism together. I came out able to argue against using quantum machine learning, which is worth more to me than being able to argue for it. ## Two ways to put a qubit inside a machine learning problem The course covers exactly two, and the difference between them is the whole map. **Quantum kernels.** A support vector machine is a classical workhorse from the 1990s, and one of the expensive parts of training one is computing the kernel matrix, the table of how similar every data point is to every other. The kernel approach replaces that one calculation and nothing else. You encode each data point into a quantum state with a feature map, the circuit that turns a data point's numbers into angles on the qubits, and the kernel becomes the overlap between two of those states, |⟨φ(x)|φ(x′)⟩|². You estimate it by running the feature map for one point, the inverse for the other, and measuring how often you land back in the all zeros state. Fill the matrix, hand it to an ordinary classical support vector machine, and it draws the boundary exactly as it always has. This is the part that made me stop. It is not a quantum computer doing machine learning. It is a quantum computer called in to compute one number, a similarity, inside a classical algorithm that stays otherwise untouched. The quantum contribution is narrow and exact. Naming it that plainly is most of understanding it. **Variational quantum classifiers.** The second approach drops the classical model and trains the circuit itself. You build a parameterised circuit, define a loss, and let a classical optimiser turn the parameters in a loop until the circuit learns to separate the classes. In Qiskit Machine Learning, a separate package on top of Qiskit, this is the VQC, or a network built from EstimatorQNN or SamplerQNN. Stack the parameters in layers and the shape resembles a neural network closely enough that the comparison is fair. The loop itself is [the same variational pattern](https://tuguidragos.com/i-earned-an-advanced-ibm-quantum-badge/) that runs quantum chemistry. Only the target changed: the cost function is now a classification loss instead of an energy. ## Most of it was machine learning I already knew Here is what the badge reminded me more than taught me. Feature spaces, training and test splits, loss functions, overfitting, the kernel trick itself. All of it is classical machine learning, decades old, already familiar. Quantum machine learning does not reinvent the classifier. It changes how one quantity gets computed and leaves the rest of the discipline standing. The quantum layer is thinner than the name suggests, and the hard part is still the machine learning. The one genuinely new judgment is the feature map. It is tempting to assume any encoding will do, and that more qubits means a better model. Neither is true. A bad feature map buys you nothing but a slower, noisier version of something a classical computer does better. An advantage requires an encoding that is hard to simulate classically, and even then it is not guaranteed. Understanding why most feature maps give you nothing was the real work. ## The honest verdict Quantum machine learning is a research area, and research is the right word. It is not a production tool. On today's noisy machines the practical advantage over classical methods is not established, the sample sizes are small, and there are open problems with training and scale that nobody has closed. I understood the techniques. I did not run them on real hardware here, and I am not going to suggest you can drop quantum machine learning into a working system tomorrow. What it gives me is the thing I came for. The place where quantum computing meets the machine learning and automation work I already do is exactly where I want to stand by the end of the decade, and this is the first credential that lands squarely on it. The intersection is thin precisely because it is hard and unproven, which is the whole reason to be in it early. > I am not claiming quantum machine learning works yet. I am claiming I now understand it well enough to tell when it does. ### The Python you actually need for quantum computing URL: https://tuguidragos.com/the-python-you-actually-need-for-quantum-computing/ Last updated: 2026-10-03T13:34:16.000Z I have written code almost every day for years. n8n flows, Python glue, the automations that quietly run a business while everyone sleeps. So when I started quantum and people kept handing me "learn Python first" guides that open with what a variable is, I closed every one of them. I did not need to learn to program. I needed to find out which parts of a language I already speak actually turn into quantum, and which parts I could keep ignoring. That turned out to be a much shorter list than I expected. The math had gone the same way. You do not relearn the whole thing. You learn the thin slice that quantum leans on, and you leave the rest exactly where it is. Here is that slice, and the pieces that genuinely surprised me along the way. ## You are not learning to program. You are learning what maps. If you have never written code, ignore this post and go do a proper beginner course first. I will link a good one at the end. But if you already build things, the framing that wasted the least of my time was this. Do not ask what Python can do. Ask which Python concepts become quantum concepts the moment you cross over. There are only a handful. A dictionary becomes your measurement result. Method chaining becomes how you read a result. Classes and objects become the reason qc.h(0) is not gibberish. And NumPy becomes the place where you can actually do quantum by hand, with no quantum library at all. Get those four and you can read and run real Qiskit. Everything else is comfort you already have, or comfort you do not need yet. ## The dictionary is your quantum result Start with the one that surprised me by how directly it carried over. A dictionary in Python holds pairs of key and value, inside curly braces. You look things up by key, not by position. If you have ever moved data between n8n nodes, you already know this object intimately. It is the JSON that flows down the pipe. ```python counts = {"00": 512, "11": 488} # a typical quantum result print(counts["00"]) # 512 print(counts.get("01", 0)) # 0, and it does not crash on a missing key for state, n in counts.items(): # walk every outcome print(state, n) ``` Here is why I am starting here. When you run a circuit and measure, Qiskit hands you exactly this. A dictionary. The key is the bit string you measured, "00" or "11" or "01". The value is how many times it came up across your shots. Reading a quantum result is reading a dictionary. The two methods you will use constantly are .get, because it does not blow up when an outcome never occurred, and .items, because that is how you loop over the whole thing to print it or plot it. So the very first quantum skill is a dictionary skill you may already have. That set the tone for the entire language. Less new than it looked. ## The everyday glue: lists and loops Two more pieces you already use without thinking. They do not become anything quantum, which is why they are not part of the four. A list is just an ordered run of values in square brackets. A for loop walks through a collection one item at a time. Between them, they are how you do anything to a result rather than just look at it. ```python counts = {"00": 512, "11": 488} total = sum(counts.values()) # 1000 shots in all top = max(counts, key=counts.get) # '00', the most frequent outcome for state, n in counts.items(): # turn raw counts into fractions print(state, round(n / total, 3)) # 00 0.512 then 11 0.488 ``` Run it. You summed the shots, found the outcome that came up most, and converted the whole result into the rough probabilities it stands for, all with a loop and a couple of built in functions. This is the kind of small handling you do constantly once results start coming back, and none of it is special to quantum. It is the same listing and looping you would do to any pile of data. If you build things already, this is muscle memory, not new ground. That is the honest shape of it. Lists and loops to handle results, a dictionary to hold them, chaining to read them, objects to build the circuit, and NumPy to check the whole thing by hand. ## Reading chained code, or why the first Qiskit line looks like noise This is a reading skill, not a writing skill, and it is the one that unlocks the most. Without it, the first line of results you see from Qiskit looks like someone fell on the keyboard. The rule is singular. You read left to right. Each piece produces an object, and the next piece acts on that object. A dot means take something from. Parentheses mean call it. Square brackets mean take the element at that index. ```python " Hello World ".strip().lower().split() # ['hello', 'world'] ``` Read it like a conveyor belt. The string goes in. .strip() removes the outer spaces and passes "Hello World" along. .lower() lowercases it and passes "hello world" along. .split() breaks it on spaces and hands you the list. Each step takes the result of the step before. If you ever lose track, stop the belt anywhere and look. " Hello World ".strip() on its own shows you the intermediate value. Indexing chains the same way. Square brackets just take an element, and you can keep going: ```python counts = {"00": 512, "11": 488} list(counts.items())[0] # ('00', 512) the first key value pair list(counts.items())[0][1] # 512 the second item of that pair ``` The first \[0\] picks the first pair out of the list. The second \[1\] reaches into that pair and takes the value. Brackets stack onto each other exactly like dots do. Same conveyor belt, same habit of stopping wherever you get lost. Now the line that scares everyone on their first real hardware run: ```python counts = job.result()[0].data.c.get_counts() ``` It looks horrible. It is the exact same conveyor belt. job.result() gives you a results object. \[0\] takes the first result, because you can submit several circuits at once. .data reaches into its data. .c is the classical register where your measurement landed, named c by default when you declare it yourself, or meas if you used measure\_all. .get\_counts() turns it into the counts dictionary from the section above. Nothing mysterious. A chain you read one link at a time. Once that clicked, half of Qiskit stopped being intimidating. It is not a new language. It is method chaining, which Python has always had. ## Objects and methods: qc.h(0) is not magic You need just enough object oriented Python to read a circuit, and not a drop more. Here is the entire amount. A class is a blueprint. An object is a thing built from that blueprint. A method is something the object can do. You build the object once, then you call methods on it to change it. ```python class Counter: def __init__(self, start=0): # runs when you build the object self.value = start # an attribute lives on the object def bump(self): # a method self.value += 1 c = Counter() # build one c.bump() c.bump() print(c.value) # 2 ``` That is genuinely all the theory you need. And here is the payoff, the moment it stops being abstract: ```python from qiskit import QuantumCircuit qc = QuantumCircuit(2) # build an object from the QuantumCircuit blueprint qc.h(0) # call the Hadamard method on qubit 0 qc.cx(0, 1) # call the CNOT method ``` QuantumCircuit is the class. qc is your object. .h(0) and .cx(0,1) are methods you call on it to add gates, a Hadamard on qubit 0 and a CNOT, the two qubit gate that flips qubit 1 whenever qubit 0 is 1\. Without this picture, that code looks like an incantation. With it, it is the most ordinary thing in the world. You made an object and you are calling methods on it, exactly like the counter. ## NumPy is the actual bridge This is the piece that changed how I learn quantum, full stop. NumPy is where you can do quantum by hand, on a normal computer, with no quantum library involved. [A qubit is a vector](https://tuguidragos.com/a-qubit-is-not-a-coin-under-a-cup/). A gate is a matrix. NumPy does vectors and matrices. So NumPy can run a quantum gate, and you get to watch the numbers move. Keep this block somewhere you can reach it. It is the bridge between the formulas and code that runs. ```python import numpy as np s = 1/np.sqrt(2) I = np.array([[1, 0], [0, 1]]) # identity, does nothing X = np.array([[0, 1], [1, 0]]) # quantum NOT: |0> <-> |1> Y = np.array([[0, -1j], [1j, 0]]) # Pauli Y, note the imaginaries Z = np.array([[1, 0], [0, -1]]) # flips the sign of |1> H = s * np.array([[1, 1], [1, -1]]) # Hadamard, builds superposition ket0 = np.array([1, 0]) # |0> ket1 = np.array([0, 1]) # |1> print(X @ ket0) # [0 1] NOT flipped the bit print(np.round(H @ ket0, 3)) # [0.707 0.707] an equal superposition ``` Run it. The @ symbol is matrix multiplication, which is the literal act of applying a gate. X @ ket0 flips a 0 into a 1\. H @ ket0 builds the superposition you read about everywhere. You are doing quantum, and it is just NumPy. And the payoff, the moment the bridge actually carries something. Turn those amplitudes into the probabilities you would measure, with one more line: ```python psi = H @ ket0 # [0.707, 0.707], an equal superposition print(np.abs(psi)**2) # [0.5 0.5], the odds of measuring 0 or 1 ``` That np.abs(psi)\*\*2 is the Born rule, the rule that converts amplitudes into measurement odds, and here it is as a single NumPy call. You built a state and read off its probabilities, by hand, before touching a single quantum library. That is the whole reason NumPy is the bridge. Everything Qiskit does to a small circuit, you can reproduce here and check against it. Three small operations carry almost all the weight, and they are worth memorising. np.abs(psi)\*\*2 turns amplitudes into probabilities, which is the Born rule in code. A.conj().T is the dagger, written A† in the books, the conjugate transpose that shows up everywhere in quantum. And np.linalg.norm(v) gives a vector's length, which for a valid quantum state must come out to 1. One surprise to file away while you are here. The textbook writes the control qubit of a two qubit gate first. Qiskit numbers its qubits the other way around, so qubit 0 is the rightmost one. It is called little endian, and it will bite you the first time a matrix from Qiskit does not match the one on your page. Not wrong, just a different convention. Knowing it exists saves an evening of confusion. ## assert is how you stop lying to yourself Both of the quantum study files I work from shout the same thing at me. Verify everything in code. This is the tool that does it, and it is two lines. assert says I am sure this is true. If it is true, nothing happens and the program carries on. If it is false, the program stops on the spot and tells you. It is a trap you set for your own mistakes. The catch with decimals is that tiny rounding errors make exact equality lie to you. You get 0.9999999999 instead of 1.0, and a plain equality check says false for no good reason. So for anything numeric you ask np.allclose, which means equal up to a speck of dust. ```python import numpy as np H = (1/np.sqrt(2)) * np.array([[1, 1], [1, -1]]) # A gate is valid only if it is unitary, meaning U dagger times U is the identity. assert np.allclose(H.conj().T @ H, np.eye(2)), "H is not unitary" # And a Hadamard applied twice should cancel: H times H is the identity. assert np.allclose(H @ H, np.eye(2)) print("verified: H is unitary and undoes itself") ``` Run it. It passes in silence, which is the point. This is the habit that made my understanding stop wobbling. I stopped trusting that I had the matrix right because it looked right, and I started checking. When a gate I built fails the unitary test, I have a bug in my understanding, right there, in red, before it poisons anything downstream. For someone whose day job is pipelines that fail silently, this felt like home. ## Seeing the result: counts to a histogram One more small piece, because a quantum result is something you look at, not just print. The standard picture is a bar chart of the counts, and you can draw it with the plotting library Matplotlib in about three lines. There is nothing quantum here either. It is a bar per key, with the count as its height. ```python import matplotlib.pyplot as plt counts = {"00": 512, "11": 488} # the result you got back plt.bar(counts.keys(), counts.values()) plt.xlabel("measured state") plt.ylabel("count") plt.show() ``` Run it and you get two bars, one for "00" and one for "11", roughly equal. That is the entanglement result everyone shows off, and you just plotted it from a plain dictionary. Qiskit ships its own prettier version called plot\_histogram, but under the hood it is doing this, walking a counts dictionary and drawing a bar for each outcome. Once you have seen the three line version, the fancy one holds no mystery. It is the dictionary, drawn. ## What I am deliberately ignoring Just as useful as the list of what to learn is the list of what to skip, so you do not drown. I am not touching decorators, generators as a craft, async, the bulk of the standard library, or the clever one line comprehensions people show off with. None of it pays off at the start. Comprehensions are nice, classes deeper than the counter above are not needed to read Qiskit, and the fancy stuff can wait for a version of me who does this full time. The integrals can wait too, same as with the math. The slice is small on purpose. If you already build things, the honest summary is this. You are not learning Python. You are learning four small mappings and one habit. Dictionary is your result. Chaining is how you read a result. Objects and methods are why the gates make sense. NumPy is where you do it by hand. And assert is how you keep yourself honest. ## A quick test before you move on Close this and check yourself, out loud or on paper. Can you read counts.get("01", 0) and say exactly what it returns and why it does not crash? Can you take the line job.result()\[0\].data.c.get\_counts() and narrate it link by link? Can you build the Hadamard as a NumPy array, apply it to |0⟩, and get \[0.707, 0.707\]? And can you prove, with assert, that it is unitary? If any of those made you hesitate, that is the piece to go back to. Not the parts that felt easy. The hesitation is the map to where your understanding is actually thin. ## Where I am learning it Free, as always. [Python for Everybody](https://www.py4e.com/) by Charles Severance is the kindest on ramp if you are genuinely new, and it does not waste your time if you are not. The NumPy documentation has a [quickstart](https://numpy.org/doc/stable/user/quickstart.html) that is short and exact. And for the bridge specifically, I just keep a single file of these blocks, the gates as arrays, the asserts, the chaining examples, and I add to it every time something surprises me. ## The thing behind the thing My job has always been the plumbing. The pipes nobody sees, carrying data from one place to another, that make the visible thing work. Quantum needed me to learn a few new pipes, that is all. A dictionary coming back from a measurement. A chain of methods reading a result. A matrix multiply that turns out to be a gate. I did not have to become a programmer to learn quantum. I already was one. I just had to find the four pipes that connect to it, and ignore the rest of the hardware store. ### A qubit is not a coin under a cup URL: https://tuguidragos.com/a-qubit-is-not-a-coin-under-a-cup/ Last updated: 2026-10-03T13:34:14.000Z There is one sentence everyone uses to explain a qubit. A qubit is both 0 and 1 at the same time. I read it a hundred times before I started learning this properly. It sounds deep. It sounds like the door to something strange and powerful. It is also the sentence that held me back the longest. I build automation pipelines, and the real skill in that is holding precise mental models of systems. If I do not know exactly what a node does to the data passing through it, the whole pipeline is a guess that happens to work until it does not. So when I started quantum and the very first idea arrived wrapped in a sentence that falls apart the moment you poke it, I did the thing I always do. I stopped and asked what is actually happening here. This is what I found. And once it clicked, half of quantum stopped being mysterious and started being mechanical. ## The sentence that explains nothing "Both 0 and 1 at the same time" does no work. Ask the obvious follow up and it collapses. If the qubit is already both, why do I only ever read one value out of it? And if it is secretly 0 or 1 and I just do not know which yet, then it is not special at all. It is a coin under a cup. Decided, hidden, boring. That second reading is the trap, and almost everyone quietly falls into it. They picture a qubit as a coin spinning in the dark, already landed, waiting for you to lift the cup and look. It feels like the safe, grown up version of the slogan. It is also wrong, and it is wrong in a way you can prove with ten lines of code. A qubit in superposition is not a hidden answer. It is not a coin that has already decided. It is a genuinely different kind of object, and the difference shows up the moment you do something a coin cannot survive. ## What it actually is Go back to the math, because the math is honest where the words are slippery. A qubit is a vector of two complex numbers. |ψ⟩ = α|0⟩ + β|1⟩ |0⟩ and |1⟩ are just names for the two definite answers, and |ψ⟩ is the qubit's state. α and β are called amplitudes. They are complex numbers, and the first thing to burn into your head is that they are not probabilities. To get a probability you take the size of an amplitude and square it. That is the Born rule: P(0) = |α|² and P(1) = |β|² The two probabilities have to add up to one, so |α|² + |β|² = 1\. That single equation is the only rule the amplitudes have to obey. Superposition just means both α and β are nonzero. The qubit is not sitting on 0 or on 1\. It is a specific blend of the two, described by those two numbers. And here is the part the coin story cannot account for. You can change the amplitudes, get a completely different qubit, and the probabilities stay exactly the same. Hold onto that. It is where the coin dies. For the record, the equal blend has a proper name. Physicists call it |+⟩, and its mirror image, the one with a minus sign between the two halves, which you will meet a few lines down, is |−⟩. Two states, identical fifty fifty probabilities, and yet completely different objects. If amplitudes were just probabilities wearing a costume, |+⟩ and |−⟩ would be the same state. They are not, and the rest of this post is really one long demonstration of why that gap is the whole point. ## The test that breaks the myth Here is the experiment. Take a qubit that starts in plain 0\. Apply a Hadamard gate, the gate whose entire job is to build a superposition. You get an equal blend. H|0⟩ = (|0⟩ + |1⟩) / √2 Square the amplitudes and both come out to one half. Fifty fifty. So far the coin story survives just fine. Measure now and you get 0 or 1, half the time each. A coin under a cup would hand you the same numbers. Now do not measure. Apply a second Hadamard instead. Think about what the coin story predicts. If the qubit were really a coin that had already secretly landed on 0 or 1, then a second shuffle is just shuffling a coin that is already decided. You stay at fifty fifty. There is no sequence of honest shuffles that takes a coin which is already heads or tails and turns it back into guaranteed heads. The qubit does exactly that. A second Hadamard on the equal blend gives you back plain 0\. Not fifty fifty. Certain. Every single shot. ```python import numpy as np ket0 = np.array([1, 0]) H = (1/np.sqrt(2)) * np.array([[1, 1], [1, -1]]) once = H @ ket0 # equal blend -> [0.707, 0.707] twice = H @ once # back to |0> -> [1, 0] print(np.abs(once)**2) # [0.5 0.5] print(np.round(np.abs(twice)**2, 12)) # [1. 0.] ``` Run it. After one Hadamard the probabilities are \[0.5, 0.5\]. After two they are \[1, 0\]. The qubit walked into a fifty fifty blend and walked back out to a guaranteed zero, with no measurement anywhere in between. A coin cannot do this. A probability cannot do this. Something else is going on, and it has a name. ## The something else is called interference Look at what the second Hadamard is doing under the hood, by hand, because it is only one line of arithmetic and it is the whole point. The blend is (|0⟩ + |1⟩)/√2\. Push each piece through the gate. The gate sends |0⟩ to (|0⟩ + |1⟩)/√2 and it sends |1⟩ to (|0⟩ − |1⟩)/√2\. Add them up, each carrying the 1/√2 from the blend: (|0⟩ + |1⟩)/2 plus (|0⟩ − |1⟩)/2 The |0⟩ parts line up and add to a full |0⟩. The |1⟩ parts have opposite signs and cancel to nothing. What survives is plain |0⟩. That cancellation is the entire story. It is called interference, and it is only possible because amplitudes are not probabilities. Probabilities are never negative. You cannot have minus thirty percent chance of rain. Amplitudes can be negative, they can be complex, and when two of them meet with opposite signs they wipe each other out. You can see that negative sign in the open. Run a Hadamard on a 1 instead of a 0: H|1⟩ = (|0⟩ − |1⟩) / √2 Same probabilities as before, one half each. But that minus sign is a real, physical difference between this state and the plus version. The probabilities cannot see it. The next gate can. That hidden sign is what physicists call relative phase, and it is exactly the thing a coin does not have. So a qubit carries more than its measurement outcomes let on. It carries the amplitudes, signs and all. Measurement throws nearly all of that away and hands you a single bit. Everything clever in quantum computing happens before measurement, in the part of the state the coin story cannot even describe. ## The myth that does the real damage There is a worse version of the slogan, and it is the one that wrecks people coming from software. It goes like this. A quantum computer tries every possible answer at the same time. Put fifty qubits in superposition and you are secretly running over a million billion calculations in parallel, for free. If that were literally true and usable, quantum computing would be trivial. You would set up the superposition, run your function, and read off the right answer. Done. Infinite parallel processors in a box. You cannot. And the reason you cannot is the most important thing on this page. Yes, all those possibilities are present in the amplitudes at once. No, you do not get to read them. Measurement does not return the whole table. It returns one row, picked at random, with odds set by the squared amplitudes. Look once, get one outcome, and the rest is gone. Here is the version that landed for me as a builder. Picture a hash table with a million keys. Superposition lets you write to every key in a single step, all at once, which sounds like a superpower. The catch is the read. You are allowed exactly one read, it returns a random key, and the act of reading wipes the table. Write to a million in parallel. Read exactly one, at random. Lose the rest. Said that way, the parallelism stops being a free lunch and becomes a hard constraint you have to engineer around. So the parallelism is real but locked. The entire game of quantum algorithm design is figuring out how to use interference to make the one outcome you are allowed to read be the one you actually want. You arrange for the amplitudes of the wrong answers to cancel and the amplitudes of the right answer to add up, before you measure. That is hard. That is why there are only a handful of quantum algorithms and not a thousand. "Tries everything at once" hides all of that difficulty behind a phrase that sounds like a free lunch. There is no free lunch. There is choreography. ## A blend is still a definite state One more correction, because this one caught me too. People say a superposition is uncertain. It is not. (|0⟩ + |1⟩)/√2 is a perfectly definite state. It has a name and an address. It is what you get by rotating 0 by ninety degrees on the sphere I describe in the next section, and there is a measurement for which it gives a single, certain answer every time, the one you get by applying a Hadamard first, the two Hadamard test above. It only looks uncertain because we keep measuring it in the 0 and 1 basis, where it happens to land fifty fifty. The line that finally fixed this for me: superposition is not the qubit being unsure. It is the qubit pointing somewhere specific that does not happen to be one of the two labels we wrote on the axes. The uncertainty is a fact about the measurement we chose, not a fact about the qubit. ## The builder's mental model Here is how I hold it now, in terms I trust. A qubit is a little arrow. Not on a number line, on a sphere. Gates are rotations of that arrow. Nothing random happens while the arrow turns. The evolution is exact, reversible, and as deterministic as any matrix multiply, because that is literally what it is, a matrix multiplying a vector. Randomness enters at exactly one place. Measurement. That is the only step that takes the smooth arrow and forces it onto a 0 or a 1, with odds set by where the arrow was pointing. Up to that instant there is no coin and no dice. There is a state vector being transformed by a pipeline of exact operations. If you come from software, that is the whole reframe. A qubit is not a probabilistic bit. It is a vector you push through a sequence of exact transformations, and the only stochastic event in the entire program is the read at the end. The "both at once" language is gesturing at the fact that the vector has two nonzero components. It is not describing a coin, and treating it like one will mislead you on every algorithm that comes later. If you want to watch the arrow instead of trust me, Qiskit will show you the state directly, no measurement involved: ```python import numpy as np from qiskit import QuantumCircuit from qiskit.quantum_info import Statevector qc = QuantumCircuit(1) qc.h(0) # one Hadamard: equal blend print(np.round(Statevector(qc).data, 3)) # [0.707+0.j 0.707+0.j] qc.h(0) # second Hadamard: interference print(np.round(Statevector(qc).data, 3)) # [1.+0.j 0.+0.j] -> back to |0> ``` Same result as the NumPy version, because it is the same math. The state vector is the truth. The probabilities are just what is left after you look. ## Why this matters, without the hype I will be blunt, because the hype around this is exhausting. Superposition on its own is not the reason quantum computers are interesting. A single qubit in a blend is not doing anything a clever classical trick cannot imitate. The power only shows up when you put many qubits in superposition together, entangle them, let their amplitudes interfere on purpose, and engineer the wrong answers to cancel while the right answer survives. That is what every real quantum algorithm is underneath. Grover, the Fourier transform, all of it. Choreographed interference. So getting superposition right is not academic tidiness. If you keep the coin picture, every algorithm later looks like magic, because interference is invisible from inside that picture. Drop the coin, keep the amplitudes, and the algorithms turn into something you can actually follow. Still hard. But followable. And superposition is only the first of two ingredients. The second is entanglement, where qubits stop having separate states of their own and only make sense together. Interference plus entanglement is where the real distance from classical computing opens up. But none of that is reachable while the first idea is still a coin in your head. So this is the floor. Get it solid and everything else has somewhere to stand. That was the trade I did not see coming. I gave up a comfortable one sentence explanation and got back a mechanical one. For someone whose job is knowing exactly what each step does to the data, that was the better deal by a mile. ## Where I am learning this All free, all worth your evening. 3Blue1Brown for seeing the vectors and the rotations instead of just reading the symbols. Khan Academy when I need to repair the algebra underneath. And for quantum specifically, running it myself in Qiskit, because nothing kills a wrong mental model faster than watching the real thing disagree with you on screen. I am keeping the whole map in one place as I go, in my own study notes, every concept and the order I ran into it. This was one of the earliest, and one of the most important to get right, because everything else is built on top of it. ## How to check whether you actually get this Reading this and nodding along is not the same as understanding it. I learned that the hard way, so here is the test I run on myself, lifted straight out of my own study notes. Close this tab and answer three things out loud, in your own words. Out loud matters. The moment you have to make a mouth say it, you find out fast whether it is real. One. Why is a qubit in superposition not the same as a coin under a cup? If your answer does not reach for the word interference, or the fact that amplitudes can be negative, you have not got it yet. You have got the slogan back. Two. After two Hadamards in a row you get a guaranteed 0\. So where did the 1 go? The honest answer is not "it became unlikely". It is "its amplitude cancelled against itself". Make sure you can say why, with the two pieces and the opposite signs. Three. A quantum computer holds every answer in its amplitudes at once. Why can you not just read them all and skip the hard part? If you cannot explain the one read, one random outcome limit, then quantum algorithms are going to keep looking like a magic trick instead of a design problem. If any of the three made you stall, that stall is the gold. It is the exact spot where your understanding is thin, and it is worth ten times more than rereading the parts you already knew. Most people do the opposite. They reread what feels comfortable and skip the part that made them stop. Go back to just the part that made you stop. And if you want the fastest way to make all three stick, do not take my word for any of it. Open Python, type out the two Hadamard example from earlier, change one number, and watch what breaks. A wrong mental model does not survive contact with code that runs. That, more than any explanation, is what moved this from "sounds right" to "I know this" for me. ## The thing behind the thing My day job is the system nobody sees that makes the visible part work. Quantum keeps turning out to be the same shape. There is the slogan on top, the one everyone repeats at parties. Under it, the actual mechanism, quieter and stranger and far more precise than the slogan lets on. A qubit is not a coin under a cup. It is a vector with two amplitudes, turning under exact rotations, uncertain about nothing, until the single moment you force it to choose. That is not a smaller idea than the slogan. It is a much bigger one. It just does not fit on a bumper sticker. ### I just earned my second IBM Quantum badge, and it says Advanced URL: https://tuguidragos.com/i-earned-an-advanced-ibm-quantum-badge/ Last updated: 2026-09-13T18:54:31.000Z A few weeks ago [I earned my first IBM Quantum badge](https://tuguidragos.com/i-earned-my-first-ibm-quantum-badge/). It was labelled Intermediate, and it bent my brain in directions it had never gone. Yesterday I earned the second one. It is labelled Advanced, and I am going to tell you exactly what that word is doing there. I am a self-taught builder. No computer science degree, no bootcamp, no corporate training program. I came into quantum computing from the world of automation and software, by reading one paragraph too many and refusing to put it down. I am telling you this again because it is the whole point of what follows. An Advanced badge from IBM is not the kind of thing the version of me from two years ago would have believed was on the table. This post is about one thing: the badge itself. What Variational Algorithm Design actually is, what it covers, and what it took to earn it. Not a plan, not a roadmap. Just the marker, and an honest look at it. ## The jump from Intermediate to Advanced My first badge, Fundamentals of Quantum Algorithms, was about the canon. Deutsch-Jozsa, Simon, Grover, the Quantum Fourier Transform. The algorithms that prove, on paper and with full rigour, that a quantum computer can do certain things with far fewer steps than a classical one. That badge taught me to reason about quantum advantage: when it appears, how large it is, and under what assumptions. This one is a different kind of hard. [Variational Algorithm Design](https://www.credly.com/badges/fbfbfd60-d63b-4288-97bb-7a1caefbe2c3/public%5Furl) is not about textbook algorithms that assume a perfect, error free machine. It is about the algorithms we can actually run on the noisy quantum computers that exist right now. That distinction matters more than almost anything else in this field, so let me be plain about it. We are in the NISQ era: Noisy Intermediate-Scale Quantum. The machines have tens to hundreds of qubits, and those qubits are noisy. There is no full error correction yet. The algorithms that run usefully on hardware like this are a specific family, and that family is exactly what this badge is about. These are near term, hybrid quantum classical algorithms. They are the strongest current candidates for real quantum advantage in the noisy era. Notice the word candidates. This is not a claim that the advantage is proven and sitting on a shelf somewhere. It is a claim that this is where the serious near term work lives. I would rather tell you that honestly than sell you something shinier. ## What Variational Algorithm Design actually covers The core idea is simpler than the name makes it sound, and genuinely beautiful once it lands. You build a quantum circuit with knobs on it. Not fixed gates, but parameterised ones, with angles you can turn. Then you set up a loop. The quantum computer runs the circuit and measures a result. A classical optimiser looks at that result, decides the knobs are not quite right, and adjusts them. Run again. Measure again. Adjust again. The quantum machine and the classical machine pass the problem back and forth until the answer settles. That loop is the whole engine, and this badge is about designing every part of it well. Here is what that breaks down into: The ansatz. This is the parameterised circuit itself: a starting reference state plus a form with tunable rotations and entangling gates. Choosing it is a real design decision. Too simple and it cannot represent the answer. Too complex and you cannot train it or run it on noisy hardware. The cost function. This is what you are trying to minimise, usually the expected value of an observable, a measurable quantity such as energy. In chemistry that observable is a Hamiltonian, the operator that encodes the molecule's energy, and the minimum is the ground state energy of a molecule. You estimate it using Qiskit Runtime primitives, the Estimator and the Sampler, which are the tools the entire course is built around. The classical optimiser. This is the part that turns the knobs. It is an ordinary classical optimisation algorithm hunting for a minimum, and picking the right one, and understanding how it behaves, is half the battle. The design tradeoffs. Weights, penalties, oversampling, undersampling. The course spends real time on the levers you pull to make a custom variational algorithm behave, not just the clean textbook version. On top of that workflow, the badge walks through the algorithms that use it. The Variational Quantum Eigensolver, or VQE, is the archetype. It finds the lowest energy state of a system, which is why it matters so much for chemistry and materials science. There are extensions of it for finding excited states, and there is the Quantum Approximate Optimization Algorithm, QAOA, which goes after combinatorial optimisation problems like max-cut, splitting a network into two halves that cut as many links as possible. At least one of the worked examples is not a simulation. It runs on real IBM quantum hardware, which is the part that keeps all of this honest. ## What it actually demanded I want to be straight about the learning curve, because a lot of quantum content is not. This badge sits on top of things you cannot skip. Linear algebra, because states are vectors and gates are matrices. The Born rule, because a measurement gives you a probabilistic outcome, not a fixed value. Expectation values, because the entire cost function is the expected value of an observable, and if that phrase does not mean something concrete to you, the variational loop is just symbols on a page. None of it is beyond a determined self-learner. I am the proof of that. But it is a real technical discipline, and the badge treats it like one. The credential is earned through an exam on IBM Training after you work through the course. Twenty questions, and you need eighty percent to pass. It is not a slideshow with a participation trophy at the end. The thing that made it click for me, the part I keep returning to, is that this is the first place where quantum and classical computing genuinely operate as one system. Not quantum replacing classical. The two of them locked in a loop, each doing the part it is good at. For someone who has spent years wiring systems together so they hand work back and forth, that architecture felt strangely familiar and completely new at the same time. That is the moment I knew this one was going to stay with me. ## Why this one matters to me I started down this road as someone who, by the usual rules, is not supposed to be here, still learning in public with all the gaps showing. An Advanced badge from IBM Quantum does not erase that starting point. It does something better. It says the starting point was never the ceiling. The work is the ceiling, and the work is doable. I am putting this on the record the same way I put everything else here: openly, with the date stamped on it, so the version of me a year from now can look back and see exactly where this point was. If you are a builder, a developer, or anyone who refuses to stay at the surface of a hard thing, you are welcome to follow along. No newsletter, no funnel, no noise. The posts are just here, in the open, as the work happens. > The first badge said Intermediate. This one says Advanced. I am exactly where I wanted to be, and I am not slowing down. ### I just earned my first IBM Quantum badge and I am not going back URL: https://tuguidragos.com/i-earned-my-first-ibm-quantum-badge/ Last updated: 2026-09-13T18:54:28.000Z I build automation pipelines: AI agents, publishing systems, n8n workflows, lead-generation engines. The real skill is not the tools. It is holding precise mental models of systems where one wrong assumption quietly breaks everything downstream. I am entirely self-taught. No CS degree, no bootcamp, no corporate onboarding program. Just time, curiosity, and a refusal to stay where I started. I tell you this because it matters for what comes next. A few weeks ago I started pulling on the quantum computing thread. Not because someone told me to, not because it was trending, but because I read one paragraph about Grover's algorithm and could not stop thinking about it. I already understood how classical computers process information. I thought I had a solid mental model of what computation actually means. Quantum broke that model in the best possible way. Yesterday, I earned the [IBM Fundamentals of Quantum Algorithms](https://www.credly.com/badges/fde7a2cf-7a89-47c4-b4ab-c599ab75878e/public%5Furl) badge through IBM Quantum. It is labelled Intermediate, and I want to be honest with you: some of it stretched my brain in directions it has never been stretched before. This post is about why I started, what the badge actually covers, and where I am going from here. ## Why quantum computing, and why now My own field moves fast, and I try to stay ahead of it. Quantum computing kept appearing in the background regardless. Research papers, job postings at IBM and Google, funding announcements, university programs spinning up dedicated faculties. It is not a trend that will peak and disappear. It is infrastructure being built right now, quietly, under the surface of everything else. What pushed me from curious to committed was realising that quantum is not faster classical computing. It is a fundamentally different model of what computation even means. A classical computer checks possibilities one after another. A quantum computer represents many possibilities at once, then uses constructive and destructive interference to cancel the wrong answers and reinforce the right one. It is not raw parallelism. It is interference. Once that clicked, I could not leave it alone. ## What the IBM Fundamentals of Quantum Algorithms badge actually covers This is not a beginner certificate where you click through slides and answer multiple choice questions. The IBM Quantum learning path is genuinely technical. Here is what the badge covers. The core quantum algorithms you need to understand: The Deutsch-Jozsa algorithm is often the first quantum algorithm people encounter because it is clean and conceptually clear. It solves a specific problem in one query that would take a deterministic classical computer exponentially many queries in the worst case. That qualifier carries the whole claim. Allow the classical machine to be randomised and to accept a small chance of being wrong, and it solves the same problem in a handful of queries, so the exponential gap holds against exact classical algorithms only. It is not practically useful on its own, but it is a clean, provable separation in the exact query model, and that is why it gets taught first. Understanding it properly requires getting comfortable with quantum oracles, black box functions you can query but not inspect, and interference. Simon's algorithm was the direct inspiration for Shor's algorithm, which factors large numbers and is the reason quantum computing gets so much attention from cryptographers and governments. Simon's algorithm finds a hidden period in a function exponentially faster than any classical approach, randomised ones included, which is the stronger kind of separation Deutsch-Jozsa does not actually give you. Working through it teaches you how quantum algorithms extract global structure from a function using interference patterns. Grover's algorithm is the one that made me stop and think when I first encountered it. It provides a quadratic speedup for unstructured search, finding one item in a pile with no index to exploit. That speedup is not exponential, but it is provably optimal for unstructured search on a quantum computer, and it applies wherever a problem can be phrased as unstructured search, though the quadratic gain is often eaten by the cost of building the oracle and by error correction overhead. The badge also covers the Quantum Fourier Transform, which is the quantum analogue of the classical discrete Fourier transform and sits at the heart of Shor's algorithm and phase estimation. Once you understand the QFT, the structure of many quantum algorithms becomes much clearer. Beyond the individual algorithms, the badge focuses on how to evaluate quantum algorithmic advantage: when does quantum actually help, by how much, and under what assumptions? This framing matters a lot if you want to work in the field seriously rather than just repeat marketing claims. ## What quantum computing actually requires you to understand I want to be direct here because I think a lot of introductory quantum content is misleading about the learning curve. To understand quantum algorithms properly, you need to be comfortable with a few things: Linear algebra is the language of quantum computing. States are vectors. Operations are matrices. Measurement is a projection. If you are not comfortable with matrix multiplication, inner products, and eigenvalues, you will hit a wall quickly. The IBM learning path assumes you can work with this notation. Complex numbers come up constantly because quantum amplitudes are complex. The interference that makes quantum algorithms work relies on amplitudes that can cancel. That is only possible because amplitudes can go negative, which probabilities never do. Complex numbers carry that sign, and the phase along with it. Probability and measurement matter because a quantum state is not just a single answer. It is a superposition of possibilities with associated amplitudes. When you measure, you collapse that superposition to a classical outcome, with probabilities determined by the amplitudes. Understanding when and how to measure is a core design decision in any quantum algorithm. None of this is impossible. I came at it from an automation background, not a physics or math degree. But I want to set accurate expectations: this is a real technical discipline, not a buzzword to put on a LinkedIn profile. ## Where I am going from here Earning this badge is the starting point, not the destination. My next concrete steps are: Learning Python for quantum using Qiskit, IBM's open source quantum computing SDK. Qiskit lets you write and simulate quantum circuits in Python, and run them on real IBM quantum hardware. I already know enough Python to get started. The goal is to implement each of the algorithms from the badge from scratch, in code, until I genuinely understand what every gate is doing. Going deeper into quantum complexity theory. Understanding the difference between BQP, the problems a quantum computer can solve efficiently, and the classical classes P and NP is important if you want to reason clearly about what quantum computers can and cannot do. There is a lot of noise in the space and a solid theoretical foundation helps filter it. Looking seriously at the intersection of quantum computing and optimisation problems, which is where I think the most near-term practical value will emerge for people coming from software and automation backgrounds. I also want to write about this journey honestly, including the parts that are hard and confusing, not just the polished insights. If you are curious about quantum computing but not sure where to start, or if you have been following the field from a distance and want to go deeper, I think we are probably in a similar position right now. ## A note on why I am sharing this I publish about AI agents, multi-agent workflows, and the practical side of building with LLMs. Quantum computing sits adjacent to that world right now, but I am convinced the overlap will grow significantly over the next five to ten years. Most quantum content online is either too shallow to be useful or so deep it assumes a physics PhD. I want to write the version that does not exist yet: honest, technical, built from scratch by someone who is not supposed to be here. If you are a developer, an automation builder, or just someone who refuses to stay at the surface of things, you might find this useful. No subscriptions, no funnel, no noise. The posts are just here, and so is the work. > The first badge is earned. The road is long. Let us see where it goes.