Maestro 0.3.1
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
SyntaxTree.h
Go to the documentation of this file.
1
12
13#pragma once
14
15#ifndef _SYNTAXTREE_H_
16#define _SYNTAXTREE_H_
17
18#include <algorithm>
19
20#include "../Circuit/Factory.h"
21#include "SimpleOps.h"
22
23namespace qasm {
25 const Expression &parameter,
26 const std::unordered_map<std::string, double> &variables) {
27 const double value = parameter.Eval(variables);
28 if (!std::isfinite(value))
29 throw std::invalid_argument("Gate parameters must be finite, got: " +
30 std::to_string(value));
31 return value;
32}
33
35 struct result {
37 };
38
40 const UopType &uop,
41 const std::unordered_map<std::string, IndexedId> &qreg_map,
42 const std::unordered_map<std::string, StatementType> &opaqueGates,
43 const std::unordered_map<std::string, StatementType> &definedGates,
44 const std::unordered_map<std::string, double> &variables = {}) const {
45 StatementType stmt;
47
48 if (std::holds_alternative<UGateCallType>(uop)) {
49 const auto &gate = std::get<UGateCallType>(uop);
50 const std::vector<Expression> &params = boost::fusion::at_c<0>(gate);
51 const ArgumentType &arg = boost::fusion::at_c<1>(gate);
52
54
55 for (const auto &p : params)
56 stmt.parameters.push_back(EvaluateGateParameter(p, variables));
57
58 stmt.qubits = ParseQubits(arg, qreg_map);
59 } else if (std::holds_alternative<CXGateCallType>(uop)) {
60 const auto &gate = std::get<CXGateCallType>(uop);
61
63
64 // two arguments
65 const ArgumentType &arg1 = boost::fusion::at_c<0>(gate);
66 const ArgumentType &arg2 = boost::fusion::at_c<1>(gate);
67
68 const std::vector<int> qubits1 = ParseQubits(arg1, qreg_map);
69 const std::vector<int> qubits2 = ParseQubits(arg2, qreg_map);
70
71 // four cases
72 if (qubits1.size() == 1 && qubits2.size() == 1) {
73 // cx q1[i], q2[j]
74 stmt.qubits.push_back(qubits1[0]);
75 stmt.qubits.push_back(qubits2[0]);
76 } else if (qubits1.size() == 1 && qubits2.size() > 1) {
77 // cx q1[i], q2
78 for (const auto &q2 : qubits2) {
79 if (q2 == qubits1[0])
80 throw std::invalid_argument(
81 "Control and target qubits cannot be the same for CX gate");
82 stmt.qubits.push_back(qubits1[0]);
83 stmt.qubits.push_back(q2);
84 }
85 } else if (qubits1.size() > 1 && qubits2.size() == 1) {
86 // cx q[], q[j]
87 for (const auto &q1 : qubits1) {
88 if (q1 == qubits2[0])
89 throw std::invalid_argument(
90 "Control and target qubits cannot be the same for CX gate");
91 stmt.qubits.push_back(q1);
92 stmt.qubits.push_back(qubits2[0]);
93 }
94 } else if (qubits1.size() == qubits2.size()) {
95 // cx q1, q2
96 for (size_t i = 0; i < qubits1.size(); ++i) {
97 if (qubits1[i] == qubits2[i])
98 throw std::invalid_argument(
99 "Control and target qubits cannot be the same for CX gate");
100 stmt.qubits.push_back(qubits1[i]);
101 stmt.qubits.push_back(qubits2[i]);
102 }
103 } else
104 throw std::invalid_argument(
105 "Mismatched qubits sizes for CX gate arguments");
106 } else if (std::holds_alternative<GatecallType>(uop)) {
107 const auto &gateCall = std::get<GatecallType>(uop);
108 // can be either simple or with expressions
109 if (std::holds_alternative<SimpleGatecallType>(gateCall)) {
110 // gate without parameters
111 const auto &gate = std::get<SimpleGatecallType>(gateCall);
112 const std::string &gateName = boost::fusion::at_c<0>(gate);
113
114 const MixedListType &args = boost::fusion::at_c<1>(gate);
115
116 // Both lookups below - the user-defined one and the builtin one - are
117 // case-sensitive, because OpenQASM is a case-sensitive language: `sdg`
118 // is a gate and `SDG` is not. They used to disagree: `definedGates`
119 // was keyed and searched by the name as written while the builtin
120 // tables were consulted with it lowercased, so every uppercase
121 // spelling of a builtin was silently accepted as that builtin -
122 // `SDG q[0];`, `RX(0.5) q[0];`, `CCX ...`, none of which is a gate in
123 // either dialect - and no user-declared gate could ever own such a
124 // name. Agreeing on the exact spelling removes the collision class
125 // entirely: `x` is the builtin, `X` is whatever the program declared,
126 // and the two coexist. (QASM2's genuinely uppercase `U` and `CX` are
127 // matched by their own rules, `ugateCall`/`cxgateCall`, which accept
128 // both cases; they reach here only via an over-long argument list,
129 // and the allowed-gate sets below list both spellings for that.)
130 const auto it = definedGates.find(gateName);
131 if (it == definedGates.end()) {
132 if (!IsSuppportedGate(gateName))
133 throw std::invalid_argument(
134 "Unsupported gate without parameters: " + gateName);
135
136 // A supported gate that is not one of the parameterless ones has
137 // arrived here with no parameter list at all - `rx q[0];` or, now
138 // that the spec-legal empty parameter list parses, `rx() q[0];`.
139 // Falling through would build the gate with its angle defaulted to
140 // zero, i.e. silently emit a different rotation than the program
141 // asked for; the missing parameters are an error instead.
142 if (!IsSuppportedNoParamGate(gateName))
143 throw std::invalid_argument("Gate " + gateName +
144 " requires parameters");
145
146 stmt.gateType = GetGateType(gateName);
147
148 // "id" is the identity gate: standard in both qelib1.inc and
149 // stdgates.inc, emitted by Qiskit, and contributing no operation.
150 // It has no gate type of its own, so it lands on kNone - whose
151 // GateNrQubits is 0, the arity the qubit-less "gphase" needs - and
152 // the generic arity below would reject its qubit argument. Its own
153 // arity is therefore spelled out by name: "id" is a one-qubit gate
154 // and takes exactly one argument (a whole-register broadcast, `id
155 // q;`, is one argument too), like any other one-qubit gate. Only
156 // "id" gets a name-specific arity, and the check itself is never
157 // skipped: skipping it let `id q[0], q[1];` and even `id q[0],
158 // q[0];` through silently, and letting kNone accept any arity would
159 // make every unmapped name a silent no-op - the very failure this
160 // guards against. The arguments are still resolved and
161 // broadcast-checked below; the resulting statement has no gate type
162 // and no gate declaration, which is what Program::AddToCircuit
163 // skips.
164 const int expectedNrQubits =
165 gateName == "id" ? 1 : GateNrQubits(stmt.gateType);
166 if (static_cast<int>(args.size()) != expectedNrQubits)
167 throw std::invalid_argument(
168 "Gate " + gateName + " requires exactly " +
169 std::to_string(expectedNrQubits) + " qubits");
170 } else {
171 // defined gate, check if the number of qubits match
172 const StatementType &definedGateStmt = it->second;
173 const int expectedNrQubits =
174 static_cast<int>(definedGateStmt.qubitsDecl.size());
175 if (static_cast<int>(args.size()) != expectedNrQubits)
176 throw std::invalid_argument(
177 "Defined gate " + gateName + " requires exactly " +
178 std::to_string(expectedNrQubits) + " qubits");
179 if (definedGateStmt.parameters.size() != 0)
180 throw std::invalid_argument(
181 "Defined gate " + gateName +
182 " requires parameters, but none were provided");
183
184 stmt.comment = gateName;
185
186 // copy the gate definition here, as it might be redefined later
187 // (actually not yet supported)
188 stmt.qubitsDecl = definedGateStmt.qubitsDecl;
189 stmt.declOps = definedGateStmt.declOps;
190 }
191
192 // first, check the qubits
193 int qubitsCounter = 1;
194
195 std::vector<std::vector<int>> allQubits(args.size());
196 for (int i = 0; i < static_cast<int>(args.size()); ++i) {
197 const ArgumentType &arg = args[i];
198 std::vector<int> qubits = ParseQubits(arg, qreg_map);
199
200 if (qubits.size() != 1) {
201 if (qubitsCounter == 1)
202 qubitsCounter = static_cast<int>(qubits.size());
203 else if (qubitsCounter != static_cast<int>(qubits.size()))
204 throw std::invalid_argument(
205 "Mismatched qubits sizes for gate arguments");
206 }
207
208 allQubits[i] = std::move(qubits);
209 }
210
211 // now set them
212 for (int i = 0; i < qubitsCounter; ++i) {
213 for (const auto &qlist : allQubits) {
214 if (qlist.size() == 1)
215 stmt.qubits.push_back(qlist[0]);
216 else
217 stmt.qubits.push_back(qlist[i]);
218 }
219 }
220 } else if (std::holds_alternative<ExpGatecallType>(gateCall)) {
221 // gate with parameters
222 const auto &gate = std::get<ExpGatecallType>(gateCall);
223 const std::string &gateName = boost::fusion::at_c<0>(gate);
224
225 const std::vector<Expression> &params = boost::fusion::at_c<1>(gate);
226 const MixedListType &args = boost::fusion::at_c<2>(gate);
227
228 for (const auto &p : params)
229 stmt.parameters.push_back(EvaluateGateParameter(p, variables));
230
231 // Case-sensitive, exactly as in the parameterless branch above: `rx`
232 // is the builtin rotation and `RX` is not a gate at all unless the
233 // program declared one by that name.
234 const auto it = definedGates.find(gateName);
235 if (it == definedGates.end()) {
236 if (IsSuppportedGate(gateName)) {
237 if (params.empty())
238 throw std::invalid_argument("Gate " + gateName +
239 " requires parameters");
240 else if (params.size() == 1) {
241 if (!IsSuppportedOneParamGate(gateName))
242 throw std::invalid_argument(
243 "This gate does not allow one parameter: " + gateName);
244 } else if (params.size() > 1) {
245 if (!IsSuppportedMultipleParamsGate(gateName))
246 throw std::invalid_argument(
247 "This gate does not allow multiple parameters: " +
248 gateName);
249
250 if (params.size() > 4)
251 throw std::invalid_argument("Too many parameters for gate: " +
252 gateName);
253 }
254
255 stmt.gateType = GetGateType(gateName);
256 const int expectedNrQubits = GateNrQubits(stmt.gateType);
257 if (static_cast<int>(args.size()) != expectedNrQubits)
258 throw std::invalid_argument(
259 "Gate " + gateName + " requires exactly " +
260 std::to_string(expectedNrQubits) + " qubits");
261
262 if (gateName == "u1") {
263 const double lambda = stmt.parameters[0];
264 stmt.parameters[0] = 0.0; // theta
265 stmt.parameters.push_back(0.);
266 stmt.parameters.push_back(lambda);
267 } else if (gateName == "u2") {
268 const double phi = stmt.parameters[0];
269 const double lambda = stmt.parameters[1];
270
271 stmt.parameters[0] = M_PI / 2.0; // theta
272 stmt.parameters[1] = phi;
273 stmt.parameters.push_back(lambda);
274 } else if (gateName == "cu1") {
275 const double lambda = stmt.parameters[0];
276 stmt.parameters[0] = 0.0; // theta
277 stmt.parameters.push_back(0.);
278 stmt.parameters.push_back(lambda);
279 } else if (gateName == "cu2") {
280 const double phi = stmt.parameters[0];
281 const double lambda = stmt.parameters[1];
282
283 stmt.parameters[0] = M_PI / 2.0; // theta
284 stmt.parameters[1] = phi;
285 stmt.parameters.push_back(lambda);
286 }
287 } else if (gateName == "u2") {
288 // Only names absent from the allowed-gate sets reach this chain:
289 // "u2" and "cu2", qelib1.inc gates with no gate type of their own.
290 // "u1", "u3", "cu1" and "cu3" are all listed in those sets, so the
291 // branch above handles them and arms for them here were dead.
293
294 if (stmt.parameters.size() != 2)
295 throw std::invalid_argument("Gate u2 requires two parameters");
296
297 const double phi = stmt.parameters[0];
298 const double lambda = stmt.parameters[1];
299
300 stmt.parameters[0] = M_PI / 2.0; // theta
301 stmt.parameters[1] = phi;
302 stmt.parameters.push_back(lambda);
303
304 if (args.size() != 1)
305 throw std::invalid_argument("Gate " + gateName +
306 " requires exactly 1 qubit");
307 } else if (gateName == "cu2") {
309
310 if (stmt.parameters.size() != 2)
311 throw std::invalid_argument("Gate cu2 requires two parameters");
312
313 const double phi = stmt.parameters[0];
314 const double lambda = stmt.parameters[1];
315
316 stmt.parameters[0] = M_PI / 2.0; // theta
317 stmt.parameters[1] = phi;
318 stmt.parameters.push_back(lambda);
319
320 if (args.size() != 2)
321 throw std::invalid_argument("Gate " + gateName +
322 " requires exactly 2 qubits");
323 } else {
324 throw std::invalid_argument("Unsupported gate with parameters: " +
325 gateName);
326 }
327 } else {
328 // defined gate, check if the number of parameters and qubits match
329 const StatementType &definedGateStmt = it->second;
330 const int expectedNrQubits =
331 static_cast<int>(definedGateStmt.qubitsDecl.size());
332 if (static_cast<int>(args.size()) != expectedNrQubits)
333 throw std::invalid_argument(
334 "Defined gate " + gateName + " requires exactly " +
335 std::to_string(expectedNrQubits) + " qubits");
336
337 if (definedGateStmt.paramsDecl.size() != stmt.parameters.size())
338 throw std::invalid_argument(
339 "Defined gate " + gateName + " requires a different number (" +
340 std::to_string(definedGateStmt.paramsDecl.size()) +
341 ") of parameters than " +
342 std::to_string(stmt.parameters.size()));
343
344 stmt.comment = gateName;
345
346 // copy the gate definition here, as it might be redefined later
347 stmt.paramsDecl = definedGateStmt.paramsDecl;
348 stmt.qubitsDecl = definedGateStmt.qubitsDecl;
349 stmt.declOps = definedGateStmt.declOps;
350 }
351
352 // first, check the qubits
353 int qubitsCounter = 1;
354
355 std::vector<std::vector<int>> allQubits(args.size());
356 for (int i = 0; i < static_cast<int>(args.size()); ++i) {
357 const ArgumentType &arg = args[i];
358 std::vector<int> qubits = ParseQubits(arg, qreg_map);
359
360 if (qubits.size() != 1) {
361 if (qubitsCounter == 1)
362 qubitsCounter = static_cast<int>(qubits.size());
363 else if (qubitsCounter != static_cast<int>(qubits.size()))
364 throw std::invalid_argument(
365 "Mismatched qubits sizes for gate arguments");
366 }
367
368 allQubits[i] = std::move(qubits);
369 }
370
371 // now set them
372 for (int i = 0; i < qubitsCounter; ++i) {
373 for (const auto &qlist : allQubits) {
374 if (qlist.size() == 1)
375 stmt.qubits.push_back(qlist[0]);
376 else
377 stmt.qubits.push_back(qlist[i]);
378 }
379 }
380 }
381 }
382
383 return stmt;
384 }
385
386 static std::vector<int> ParseQubits(
387 const ArgumentType &arg,
388 const std::unordered_map<std::string, IndexedId> &qreg_map) {
389 return ResolveRegisterOperand(arg, qreg_map, "quantum");
390 }
391
392 static bool IsSuppportedNoParamGate(const std::string &gateName) {
393 return allowedNoParamGates.find(gateName) != allowedNoParamGates.end();
394 }
395
396 static bool IsSuppportedOneParamGate(const std::string &gateName) {
397 return allowedOneParamGates.find(gateName) != allowedOneParamGates.end();
398 }
399
400 static bool IsSuppportedMultipleParamsGate(const std::string &gateName) {
401 return allowedMultipleParamsGates.find(gateName) !=
402 allowedMultipleParamsGates.end();
403 }
404
405 static bool IsSuppportedGate(const std::string &gateName) {
406 return allowedNoParamGates.find(gateName) != allowedNoParamGates.end() ||
407 allowedOneParamGates.find(gateName) != allowedOneParamGates.end() ||
408 allowedMultipleParamsGates.find(gateName) !=
409 allowedMultipleParamsGates.end();
410 }
411
412 // Case-sensitive, like the allowed-gate sets it is paired with: it maps the
413 // canonical lowercase spelling of a builtin and nothing else. It used to
414 // lowercase its argument, which was one half of the mismatch that let an
415 // uppercase call reach a builtin while user-defined gates were looked up as
416 // written - see the note at the SimpleGatecallType branch above.
417 Circuits::QuantumGateType GetGateType(const std::string &gateName) const {
418 // "U" and "CX" are the two QASM2 builtins that are legitimately spelled
419 // in uppercase, and both spellings of each are accepted - see the note on
420 // the allowed-gate sets below. They are the only uppercase names here.
421 if (gateName == "x")
423 else if (gateName == "y")
425 else if (gateName == "z")
427 else if (gateName == "h")
429 else if (gateName == "s")
431 else if (gateName == "sdg" || gateName == "sdag")
433 else if (gateName == "t")
435 else if (gateName == "tdg" || gateName == "tdag")
437 else if (gateName == "sx")
439 else if (gateName == "sxdg" || gateName == "sxdag")
441 else if (gateName == "k")
443 else if (gateName == "swap")
445 else if (gateName == "cx" || gateName == "CX")
447 else if (gateName == "cy")
449 else if (gateName == "cz")
451 else if (gateName == "ch")
453 else if (gateName == "csx")
455 else if (gateName == "csxdg" || gateName == "csxdag")
457 else if (gateName == "cswap")
459 else if (gateName == "ccx")
461 else if (gateName == "p" || gateName == "phase")
463 else if (gateName == "rx")
465 else if (gateName == "ry")
467 else if (gateName == "rz")
469 else if (gateName == "cp" || gateName == "cphase")
471 else if (gateName == "crx")
473 else if (gateName == "cry")
475 else if (gateName == "crz")
477 else if (gateName == "U" || gateName == "u" || gateName == "u3" ||
478 gateName == "u1")
480 else if (gateName == "cu" || gateName == "cu3" || gateName == "cu1")
482
483 // Returned for an unmapped name, and for the two accepted names that
484 // legitimately have no gate of their own: 'id' and 'gphase', both of
485 // which are dropped rather than emitted. The callers pair this with an
486 // arity check, so an unmapped name is still rejected rather than being
487 // silently treated as a no-op.
489 }
490
492 const int gateT = static_cast<int>(gateType);
493
494 if (gateT < static_cast<int>(Circuits::QuantumGateType::kSwapGateType))
495 return 1;
496 else if (gateT <
498 return 2;
499 else if (gateT <= static_cast<int>(Circuits::QuantumGateType::kCCXGateType))
500 return 3;
501
502 return 0;
503 }
504
505 // The accepted gate names, exposed so a regression test can walk every one
506 // of them and check it actually resolves. Both of the gate-name bugs this
507 // guards against were a name listed here with no matching arm in
508 // GetGateType, which no test could reach without the lists themselves.
509 static const std::unordered_set<std::string> &AllowedNoParamGates() {
510 return allowedNoParamGates;
511 }
512
513 static const std::unordered_set<std::string> &AllowedOneParamGates() {
514 return allowedOneParamGates;
515 }
516
517 static const std::unordered_set<std::string> &AllowedMultipleParamsGates() {
518 return allowedMultipleParamsGates;
519 }
520
521 private:
522 // Gate names are matched exactly - OpenQASM is case-sensitive, so "sdg" is
523 // a gate and "SDG" is not - with exactly two exceptions: "CX" and "U", the
524 // QASM2 builtins the language itself spells in uppercase. Their own call
525 // rules (`cxgateCall`/`ugateCall` in qasm.h) already accept either case, so
526 // both spellings are listed here too. That matters only for the calls that
527 // fall out of those rules - an over-long argument list, which `gatecall`
528 // picks up - and it is what keeps `U(...) q[0], q[1];` reported as the
529 // qubit-count error it is instead of as an unknown gate.
530 static inline std::unordered_set<std::string> allowedNoParamGates = {
531 "x", "y", "z", "h", "s", "sdg", "sdag", "t", "tdg",
532 "tdag", "sx", "sxdg", "sxdag", "k", "swap", "cx", "CX", "cy",
533 "cz", "ch", "csx", "csxdg", "csxdag", "cswap", "ccx", "id"};
534 static inline std::unordered_set<std::string> allowedOneParamGates = {
535 "p", "rx", "ry", "rz", "cp", "crx", "cry",
536 "crz", "u1", "cu1", "phase", "cphase", "gphase"};
537 // "cu1" is deliberately absent: like "u1" it takes exactly one angle, and
538 // listing it here made cu1(a, b, c) accepted and silently lowered as cu3.
539 static inline std::unordered_set<std::string> allowedMultipleParamsGates = {
540 "u", "U", "u3", "cu", "cu3"}; // max 4
541};
542
543inline phx::function<AddGateExpr> AddGate;
544
545// Lowers a QASM3 modified gate call by rewriting it into the equivalent
546// unmodified call and delegating to AddGateExpr. It wraps AddGateExpr rather
547// than modifying it, because AddGateExpr is also the macro inliner used by
548// Program::AddToCircuit for user-defined gates; with an empty modifier list
549// this delegates straight through, so an unmodified call behaves exactly as
550// it did before.
552 struct result {
554 };
555
557 const ModifiedUopType &modifiedUop,
558 const std::unordered_map<std::string, IndexedId> &qreg_map,
559 const std::unordered_map<std::string, StatementType> &opaqueGates,
560 const std::unordered_map<std::string, StatementType> &definedGates,
561 const std::unordered_map<std::string, double> &variables = {}) const {
562 const ModifierListType &modifiers = boost::fusion::at_c<0>(modifiedUop);
563 const UopType &uop = boost::fusion::at_c<1>(modifiedUop);
564
565 AddGateExpr addGate;
566 if (modifiers.empty())
567 return addGate(uop, qreg_map, opaqueGates, definedGates, variables);
568
569 std::string gateName;
570 std::vector<double> params;
571 MixedListType args;
572 Canonicalize(uop, gateName, params, args, variables);
573
574 if (definedGates.find(gateName) != definedGates.end())
575 throw std::invalid_argument(
576 "Gate modifiers cannot be applied to the user-defined gate: " +
577 gateName);
578
579 // Only aliases are folded here, not case: gate names are matched exactly,
580 // so `inv @ SDG` is no more a call to sdg than the unmodified `SDG q[0];`
581 // is. Lowercasing here would have reintroduced, on the modified path
582 // alone, the very silent uppercase-reaches-a-builtin behaviour the plain
583 // call path no longer has. Runs after the user-defined lookup above,
584 // which must see the name exactly as written.
585 std::string canonicalName = gateName;
586 NormalizeGateName(canonicalName);
587
588 // `ctrl(n) @` contributes n controls, not one, so the count is summed
589 // rather than the modifiers counted - this is what makes `ctrl(3) @ x`
590 // report the multi-control limit below instead of being lowered as a
591 // single control.
592 size_t nrControls = 0;
593 for (const auto &modifier : modifiers)
594 if (IsControl(modifier))
595 nrControls += static_cast<size_t>(modifier.count);
596
597 if (nrControls > 2)
598 throw std::invalid_argument(
599 "ctrl @ with more than two controls is not supported, " +
600 std::to_string(nrControls) +
601 " were requested for the gate: " + canonicalName);
602
603 // Identity remains a no-op under every modifier, but its call still has
604 // to satisfy the same operand, arity, broadcast-width and parameter
605 // checks as any other modified gate. Validate through the equivalent
606 // one-, two- or three-qubit gate shape before dropping it.
607 if (canonicalName == "id") {
608 static constexpr const char *validationGates[] = {"id", "cx", "ccx"};
609 addGate(MakeCall(validationGates[nrControls], params, args), qreg_map,
610 opaqueGates, definedGates);
611 return NoOpStatement();
612 }
613
614 // The number of times the rewritten gate is emitted; pow(k) on a gate
615 // whose angle cannot be scaled turns into repetition instead.
616 int repetitions = 1;
617
618 // Modifiers apply innermost first, i.e. right to left in source order.
619 for (auto it = modifiers.rbegin(); it != modifiers.rend(); ++it) {
620 switch (it->kind) {
622 ApplyInv(canonicalName, params);
623 break;
625 ApplyPow(it->exponent, canonicalName, params, repetitions);
626 break;
629 // negctrl is lowered as a positive control here and conjugated with
630 // x gates on the negated control below. `ctrl(n) @` is n nested
631 // single controls, e.g. ctrl(2) @ x is ctrl @ ctrl @ x, i.e. ccx.
632 for (int c = 0; c < it->count; ++c) ApplyCtrl(canonicalName, params);
633 break;
634 }
635 }
636
637 // The rewritten call is validated even when it is ultimately dropped or
638 // repeated, so pow(0) @ h q[0], q[1] is still a qubit-count error.
639 StatementType stmt = addGate(MakeCall(canonicalName, params, args),
640 qreg_map, opaqueGates, definedGates);
641
642 if (repetitions == 0) return NoOpStatement();
643
644 // Program::AddToCircuit emits one gate per group of qubits, so repeating
645 // the resolved qubit list repeats the gate - including under
646 // broadcasting, where the whole broadcast round is repeated.
647 const std::vector<int> singleRound = stmt.qubits;
648 for (int r = 1; r < repetitions; ++r)
649 stmt.qubits.insert(stmt.qubits.end(), singleRound.begin(),
650 singleRound.end());
651
652 const std::vector<size_t> negatedControls = NegatedControls(modifiers);
653 if (negatedControls.empty()) return stmt;
654
655 return ConjugateNegatedControls(stmt, canonicalName, params,
656 negatedControls);
657 }
658
659 // The qubit positions a negctrl modifier controls. Control modifiers
660 // consume qubit arguments left to right in source order - one each for the
661 // countless spelling, `count` of them for `negctrl(n) @`, all of which are
662 // negated - so a control modifier owns as many consecutive qubit arguments
663 // as its count. inv and pow, which consume none, are skipped.
664 static std::vector<size_t> NegatedControls(
665 const ModifierListType &modifiers) {
666 std::vector<size_t> negatedControls;
667 size_t controlPos = 0;
668
669 for (const auto &modifier : modifiers) {
670 if (!IsControl(modifier)) continue;
671 for (int c = 0; c < modifier.count; ++c, ++controlPos)
672 if (modifier.kind == ModifierKind::NegCtrl)
673 negatedControls.push_back(controlPos);
674 }
675
676 return negatedControls;
677 }
678
679 // `negctrl @ g c, t` means "control on |0>", which is `x c; ctrl @ g c, t;
680 // x c;`. A qop synthesizes a single statement, so the triple is emitted
681 // through the defined-gate inlining path already in Program::AddToCircuit:
682 // a Uop with no gate type, a qubitsDecl naming the gate's qubits and a
683 // declOps list is expanded op by op, with the resolved qubits substituted
684 // for the declared names. Parameters are baked into declOps as constants,
685 // so the synthetic declaration needs none.
687 const QoperationStatement &gate, const std::string &gateName,
688 const std::vector<double> &params,
689 const std::vector<size_t> &negatedControls) {
690 StatementType stmt;
693 stmt.qubits = gate.qubits;
694
695 const int nrQubits = AddGateExpr::GateNrQubits(gate.gateType);
696 for (int q = 0; q < nrQubits; ++q)
697 stmt.qubitsDecl.push_back("__negctrl_q" + std::to_string(q));
698
699 MixedListType gateArgs;
700 for (const auto &qubitName : stmt.qubitsDecl)
701 gateArgs.push_back(ArgumentType(qubitName));
702
703 MixedListType flipArgs;
704 for (const size_t control : negatedControls)
705 flipArgs.push_back(ArgumentType(stmt.qubitsDecl[control]));
706
707 for (const auto &flipArg : flipArgs)
708 stmt.declOps.push_back(MakeCall("x", {}, MixedListType{flipArg}));
709 stmt.declOps.push_back(MakeCall(gateName, params, gateArgs));
710 for (const auto &flipArg : flipArgs)
711 stmt.declOps.push_back(MakeCall("x", {}, MixedListType{flipArg}));
712
713 return stmt;
714 }
715
716 // Flattens any of the three uop shapes into the (name, evaluated
717 // parameters, qubit arguments) triple the rewriting below works on. The
718 // dedicated U and CX call rules carry no gate name of their own, so they
719 // get their canonical one here.
720 //
721 // `variables` is the same map AddModifiedGateExpr::operator() received -
722 // top-level `input` bindings, since modifiers cannot appear inside a gate
723 // declaration body, so no macro formal parameter is ever in scope here.
724 // Passing it through (rather than an empty map) is what lets a modified
725 // call's own parameter expression, e.g. `ctrl @ rz(theta) ...`, resolve an
726 // `input` the same way the unmodified path (AddGateExpr, reached when the
727 // modifier list is empty) already does. Any identifier not found in it is
728 // still an error, not a silent zero - see Variable::Eval.
729 static void Canonicalize(
730 const UopType &uop, std::string &gateName, std::vector<double> &params,
731 MixedListType &args,
732 const std::unordered_map<std::string, double> &variables) {
733 if (std::holds_alternative<UGateCallType>(uop)) {
734 const auto &gate = std::get<UGateCallType>(uop);
735 gateName = "u";
736 for (const auto &p : boost::fusion::at_c<0>(gate))
737 params.push_back(EvaluateGateParameter(p, variables));
738 args.push_back(boost::fusion::at_c<1>(gate));
739 } else if (std::holds_alternative<CXGateCallType>(uop)) {
740 const auto &gate = std::get<CXGateCallType>(uop);
741 gateName = "cx";
742 args.push_back(boost::fusion::at_c<0>(gate));
743 args.push_back(boost::fusion::at_c<1>(gate));
744 } else {
745 const auto &gateCall = std::get<GatecallType>(uop);
746 if (std::holds_alternative<SimpleGatecallType>(gateCall)) {
747 const auto &gate = std::get<SimpleGatecallType>(gateCall);
748 gateName = boost::fusion::at_c<0>(gate);
749 args = boost::fusion::at_c<1>(gate);
750 } else {
751 const auto &gate = std::get<ExpGatecallType>(gateCall);
752 gateName = boost::fusion::at_c<0>(gate);
753 for (const auto &p : boost::fusion::at_c<1>(gate))
754 params.push_back(EvaluateGateParameter(p, variables));
755 args = boost::fusion::at_c<2>(gate);
756 }
757 }
758 }
759
760 // Maps the accepted spellings of a gate onto the one name the modifier
761 // tables are keyed by, so that `ctrl @ phase(0.3)` behaves like
762 // `ctrl @ p(0.3)`. Only aliases GetGateType maps identically are listed,
763 // which keeps a modified call accepting exactly what the same unmodified
764 // call accepts - including the "dag" spellings, each of which GetGateType
765 // now resolves to the same gate as its "dg" twin.
766 static void NormalizeGateName(std::string &gateName) {
767 static const std::unordered_map<std::string, std::string> aliases = {
768 {"phase", "p"}, {"cphase", "cp"}, {"u3", "u"},
769 {"cu3", "cu"}, {"sdag", "sdg"}, {"sxdag", "sxdg"},
770 {"tdag", "tdg"}, {"csxdag", "csxdg"}};
771
772 const auto it = aliases.find(gateName);
773 if (it != aliases.end()) gateName = it->second;
774 }
775
776 // Builds the rewritten, unmodified gate call. Parameters are re-emitted as
777 // Constant expressions, so all of AddGateExpr's qubit resolution,
778 // broadcasting and validation applies to it unchanged.
779 static UopType MakeCall(const std::string &gateName,
780 const std::vector<double> &params,
781 const MixedListType &args) {
782 if (params.empty()) return GatecallType(SimpleGatecallType(gateName, args));
783
784 std::vector<Expression> paramExprs;
785 for (const double p : params) paramExprs.push_back(Expression(Constant(p)));
786
787 return GatecallType(ExpGatecallType(gateName, paramExprs, args));
788 }
789
790 // A statement that adds nothing to the circuit: Program::AddToCircuit skips
791 // a Uop with no gate type and no gate declaration.
799
800 static void ApplyInv(std::string &gateName, std::vector<double> &params) {
801 static const std::unordered_set<std::string> selfInverseGates = {
802 "x", "y", "z", "h", "cx", "cy", "cz", "ch", "swap", "cswap", "ccx"};
803 static const std::unordered_map<std::string, std::string> daggerGates = {
804 {"s", "sdg"}, {"sdg", "s"}, {"t", "tdg"}, {"tdg", "t"},
805 {"sx", "sxdg"}, {"sxdg", "sx"}, {"csx", "csxdg"}, {"csxdg", "csx"}};
806
807 if (selfInverseGates.find(gateName) != selfInverseGates.end()) return;
808
809 const auto daggerIt = daggerGates.find(gateName);
810 if (daggerIt != daggerGates.end()) {
811 gateName = daggerIt->second;
812 return;
813 }
814
815 if (rotationGates.find(gateName) != rotationGates.end()) {
816 for (auto &p : params) p = -p;
817 return;
818 }
819
820 if (gateName == "u" || gateName == "cu") {
821 // The gate is u(theta, phi, lambda, gamma) = exp(i*gamma) *
822 // u(theta, phi, lambda) - the fourth parameter is a global phase - so
823 // the inverse is u(-theta, -lambda, -phi, -gamma): phi and lambda swap
824 // as well as being negated.
825 params.resize(std::max<size_t>(params.size(), 3), 0.);
826 std::swap(params[1], params[2]);
827 for (auto &p : params) p = -p;
828 return;
829 }
830
831 throw std::invalid_argument("inv @ is not supported for the gate: " +
832 gateName);
833 }
834
835 static void ApplyPow(double exponent, std::string &gateName,
836 std::vector<double> &params, int &repetitions) {
837 if (!std::isfinite(exponent))
838 throw std::invalid_argument(
839 "pow(k) @ requires a finite exponent for "
840 "the gate: " +
841 gateName);
842
843 if (exponent < 0.) {
844 // g^(-k) is (g^-1)^k.
845 ApplyInv(gateName, params);
846 exponent = -exponent;
847 }
848
849 if (exponent == 0.) {
850 repetitions = 0;
851 return;
852 }
853
854 // Scaling the angle is exact for any real exponent and produces a single
855 // gate, so it is preferred over repetition even for integer exponents.
856 if (rotationGates.find(gateName) != rotationGates.end()) {
857 for (auto &p : params) p *= exponent;
858 return;
859 }
860
861 const double rounded = std::round(exponent);
862 if (std::abs(exponent - rounded) > 1e-12)
863 throw std::invalid_argument(
864 "pow(k) @ with a fractional exponent is not supported for the "
865 "gate: " +
866 gateName);
867
868 // The gate is materialised once per repetition, so an unbounded exponent
869 // would overflow the cast to int - undefined behaviour, and a silently
870 // wrong repetition count - and allocate before emitting anything.
871 if (rounded > static_cast<double>(kMaxRepetitions) ||
872 static_cast<double>(repetitions) * rounded >
873 static_cast<double>(kMaxRepetitions))
874 throw std::invalid_argument(
875 "pow(k) @ with an exponent above " + std::to_string(kMaxRepetitions) +
876 " is not supported for the gate: " + gateName);
877
878 repetitions *= static_cast<int>(rounded);
879 }
880
881 static void ApplyCtrl(std::string &gateName, std::vector<double> &params) {
882 static const std::unordered_map<std::string, std::string> controlledGates =
883 {{"x", "cx"}, {"y", "cy"}, {"z", "cz"}, {"h", "ch"},
884 {"sx", "csx"}, {"sxdg", "csxdg"}, {"p", "cp"}, {"rx", "crx"},
885 {"ry", "cry"}, {"rz", "crz"}, {"swap", "cswap"}, {"cx", "ccx"}};
886 // A controlled diagonal-phase gate is exactly cp at the matching angle,
887 // since t == p(pi/4) and s == p(pi/2). This is what keeps every lowering
888 // on a gate type that already exists.
889 static const std::unordered_map<std::string, double> phaseAngles = {
890 {"s", M_PI / 2.},
891 {"sdg", -M_PI / 2.},
892 {"t", M_PI / 4.},
893 {"tdg", -M_PI / 4.}};
894
895 if (gateName == "gphase")
896 throw std::invalid_argument(
897 "Controlled global phase ('ctrl @ gphase') is not supported by "
898 "the Circuit IR.");
899
900 if (gateName == "u") {
901 // cu takes the same (theta, phi, lambda[, gamma]) parameters as u; a
902 // shorter u call leaves the remaining angles at zero.
903 params.resize(std::max<size_t>(params.size(), 3), 0.);
904 gateName = "cu";
905 return;
906 }
907
908 const auto controlledIt = controlledGates.find(gateName);
909 if (controlledIt != controlledGates.end()) {
910 gateName = controlledIt->second;
911 return;
912 }
913
914 const auto phaseIt = phaseAngles.find(gateName);
915 if (phaseIt != phaseAngles.end()) {
916 gateName = "cp";
917 params.assign(1, phaseIt->second);
918 return;
919 }
920
921 throw std::invalid_argument("ctrl @ is not supported for the gate: " +
922 gateName);
923 }
924
925 static bool IsControl(const ModifierType &modifier) {
926 return modifier.kind == ModifierKind::Ctrl ||
927 modifier.kind == ModifierKind::NegCtrl;
928 }
929
930 private:
931 // Gates whose single parameter is an angle they are linear in, so that
932 // g(theta)^s == g(s * theta) for any real s.
933 static inline std::unordered_set<std::string> rotationGates = {
934 "rx", "ry", "rz", "p", "cp", "crx", "cry", "crz"};
935
936 // The largest number of copies pow(k) may expand a gate into.
937 static constexpr int kMaxRepetitions = 1024;
938};
939
940inline phx::function<AddModifiedGateExpr> AddModifiedGate;
941
942// `gatedeclop` keeps using the plain `uop`, since using `modifiedUop` there
943// would change the element type of StatementType::declOps and ripple into the
944// inliner. A modifier in a gate body would otherwise leave the declaration
945// unparsable and be reported as leftover input, so it is caught here and
946// named. Nothing else in a gate body can start with a modifier, so this
947// action only ever runs on a committed parse.
949 struct result {
950 typedef UopType type;
951 };
952
953 UopType operator()(const ModifierListType & /*modifiers*/) const {
954 throw std::invalid_argument(
955 "Gate modifiers (ctrl, negctrl, inv, pow) are not supported inside a "
956 "gate declaration body");
957 }
958};
959
960inline phx::function<RejectGateBodyModifierExpr> RejectGateBodyModifier;
961
962// QASM3 constructs outside our supported subset (for/while loops, subroutine
963// definitions, register aliases, duration/delay, box blocks, array
964// declarations) are recognised by keyword at statement position - see
965// `unsupportedConstruct` in qasm.h - and rejected by name instead of falling
966// through to Spirit's generic "unparsed input" error or being misparsed as a
967// gate call. The recognising rule already carries the per-construct message
968// as its attribute (looked up from the unsupportedKeywords symbol table), so
969// this functor only has to throw it.
971 struct result {
973 };
974
975 QoperationStatement operator()(const std::string &message) const {
976 throw std::invalid_argument(message);
977 }
978};
979
980inline phx::function<RejectUnsupportedConstructExpr> RejectUnsupportedConstruct;
981
982// 'phase', 'cphase' and 'gphase' are OpenQASM 3 stdgates.inc names: none of
983// them appears in any version of qelib1.inc, including the extended one
984// Qiskit ships, so none of them is a gate under an `OPENQASM 2.0;` header.
985//
986// The dialect is not visible where the allowed-gate sets and GetGateType are
987// consulted, and threading it there would have made GetGateType something
988// other than the pure name -> type function it is. So the filter sits in the
989// grammar instead, at `qasm2RejectedGate` in qasm.h - the one place that does
990// know the dialect - and the gate tables stay dialect-agnostic.
991//
992// Two outcomes rather than one, hence the bool result. Under QASM2 these are
993// ordinary identifiers, so a program may legitimately declare a gate of its
994// own by one of these names; when it has, the rule *fails* (returns false)
995// and the call falls through to `gatecall`, which resolves it against the
996// declaration. Otherwise it is a call to a gate that does not exist in this
997// dialect, and that is thrown by name - the point of the rule being that the
998// diagnostic says so rather than leaving a bare "unparsed input".
1000 template <typename, typename>
1001 struct result {
1002 typedef bool type;
1003 };
1004
1005 bool operator()(const std::string &gateName,
1006 const std::unordered_map<std::string, StatementType>
1007 &definedGates) const {
1008 if (definedGates.find(gateName) != definedGates.end()) return false;
1009
1010 const std::string qasm2Spelling = gateName == "phase" ? "p"
1011 : gateName == "cphase" ? "cp"
1012 : "";
1013
1014 throw std::invalid_argument(
1015 "'" + gateName +
1016 "' is an OpenQASM 3 stdgates.inc gate and is not available under "
1017 "'OPENQASM 2.0;'" +
1018 (qasm2Spelling.empty()
1019 ? " (it has no qelib1.inc equivalent)"
1020 : "; the qelib1.inc spelling is '" + qasm2Spelling + "'"));
1021 }
1022};
1023
1024inline phx::function<RejectQasm3OnlyGateExpr> RejectQasm3OnlyGate;
1025
1026template <class Time>
1027inline std::shared_ptr<Circuits::ICondition> MakeEqualCondition(
1028 const std::vector<int> &conditionBits,
1029 const std::vector<bool> &expectedValues) {
1030 if (conditionBits.size() != expectedValues.size())
1031 throw std::invalid_argument(
1032 "Condition bit indices and expected values must have the same width.");
1033
1034 std::vector<size_t> ind;
1035 ind.reserve(conditionBits.size());
1036 for (const int bit : conditionBits) ind.push_back(static_cast<size_t>(bit));
1037
1039 expectedValues);
1040}
1041
1042inline std::vector<bool> DecodeRegisterCondition(
1043 const std::string &registerName, int value, size_t width) {
1044 if (value < 0)
1045 throw std::invalid_argument("Condition value for classical register '" +
1046 registerName + "' must be non-negative.");
1047
1048 const auto unsignedValue = static_cast<unsigned int>(value);
1049 if (width < std::numeric_limits<unsigned int>::digits &&
1050 unsignedValue >= (1u << width))
1051 throw std::invalid_argument("Condition value " + std::to_string(value) +
1052 " does not fit classical register '" +
1053 registerName + "' of width " +
1054 std::to_string(width) + ".");
1055
1056 std::vector<bool> expected(width, false);
1057 auto remaining = unsignedValue;
1058 for (size_t bit = 0; bit < width && remaining != 0; ++bit) {
1059 expected[bit] = (remaining & 1u) != 0;
1060 remaining >>= 1;
1061 }
1062 return expected;
1063}
1064
1065// Measurement/Reset keep their own cbits (the targets) and take the condition
1066// in condBits; everything else becomes a CondUop conditioned via cbits.
1068 const std::vector<int> &conditionBits,
1069 const std::vector<bool> &expectedValues) {
1070 stmt.condExpected = expectedValues;
1073 stmt.condBits = conditionBits;
1074 return;
1075 }
1076
1078 stmt.cbits = conditionBits;
1079}
1080
1082 struct result {
1084 };
1085
1087 CondOpType &condOp,
1088 const std::unordered_map<std::string, IndexedId> &qreg_map,
1089 const std::unordered_map<std::string, IndexedId> &creg_map,
1090 const std::unordered_map<std::string, StatementType> &opaqueGates,
1091 const std::unordered_map<std::string, StatementType> &definedGates)
1092 const {
1093 StatementType stmt;
1094
1095 const std::string &condId = boost::fusion::at_c<0>(condOp);
1096 int condVal = boost::fusion::at_c<1>(condOp);
1097 const QoperationStatement &op = boost::fusion::at_c<2>(condOp);
1098
1099 stmt = op;
1100
1101 const std::vector<int> conditionBits =
1102 ResolveRegisterOperand(ArgumentType(condId), creg_map, "classical");
1104 stmt, conditionBits,
1105 DecodeRegisterCondition(condId, condVal, conditionBits.size()));
1106
1107 return stmt;
1108 }
1109};
1110
1111inline phx::function<AddCondQopExpr> AddCondQop;
1112
1113// Conditions one braced-conditional body on the given bits. Shared by the
1114// if-branch and the else-branch, which differ only in the expected values.
1116 struct result {
1117 typedef std::vector<QoperationStatement> type;
1118 };
1119
1120 std::vector<QoperationStatement> operator()(
1121 const std::vector<QopType> &body, const std::vector<int> &absoluteBits,
1122 const std::vector<bool> &expectedValues) const {
1123 std::vector<QoperationStatement> stmts;
1124 stmts.reserve(body.size());
1125
1126 for (const auto &bodyStmt : body) {
1127 StatementType stmt = bodyStmt;
1128 ApplyCondition(stmt, absoluteBits, expectedValues);
1129 stmts.push_back(stmt);
1130 }
1131
1132 return stmts;
1133 }
1134};
1135
1137 const std::vector<QopType> &body, const std::vector<int> &conditionBits,
1138 bool followedByElse, std::string_view branchName) {
1139 for (size_t statementIndex = 0; statementIndex < body.size();
1140 ++statementIndex) {
1141 const auto &stmt = body[statementIndex];
1143 continue;
1144
1145 const bool writesPredicate =
1146 std::any_of(stmt.cbits.begin(), stmt.cbits.end(), [&](int targetBit) {
1147 return std::find(conditionBits.begin(), conditionBits.end(),
1148 targetBit) != conditionBits.end();
1149 });
1150 const bool predicateWillBeReadAgain =
1151 statementIndex + 1 < body.size() || followedByElse;
1152 if (writesPredicate && predicateWillBeReadAgain)
1153 throw std::invalid_argument(
1154 "A measurement in the " + std::string(branchName) +
1155 " branch changes a predicate bit before the braced condition is "
1156 "evaluated again. The Circuit IR cannot preserve the source-level "
1157 "single evaluation of that predicate.");
1158 }
1159}
1160
1161// A QASM3 braced conditional, `if ( <condHead> ) { <body> }`, with an
1162// optional `else { <elseBody> }`. `condHead` (see CondHeadType) already
1163// tells us which of the two condition shapes we parsed:
1164//
1165// - Register form (`c == 2`): delegates per-statement cbit population to
1166// the existing AddCondQopExpr, one call per body statement, exactly as
1167// before this else-handling was added. `else` is rejected here rather
1168// than silently mishandled: expressing "the register does NOT equal 2"
1169// would need a not-equal condition primitive, and Circuit/Factory.h only
1170// offers CreateEqualCondition (see CircuitFactory::CreateEqualCondition
1171// in Circuit/Factory.h) - no not-equal primitive exists to build the
1172// else-branch's condition, and adding one is out of scope here.
1173//
1174// - Bit form (`c[0]` / `!c[0]` / `&&` chain): both branches are expressible
1175// with CreateEqualCondition on the very same bits - the else-branch is just
1176// the complementary expected value, so it is supported for a single bit.
1178 struct result {
1179 typedef std::vector<QoperationStatement> type;
1180 };
1181
1182 std::vector<QoperationStatement> operator()(
1183 const CondHeadType &condHead, const std::vector<QopType> &body,
1184 const boost::optional<std::vector<QopType>> &elseBody,
1185 const std::unordered_map<std::string, IndexedId> &qreg_map,
1186 const std::unordered_map<std::string, IndexedId> &creg_map,
1187 const std::unordered_map<std::string, StatementType> &opaqueGates,
1188 const std::unordered_map<std::string, StatementType> &definedGates)
1189 const {
1190 if (condHead.isBitForm) {
1191 std::vector<int> absoluteBits;
1192 std::vector<bool> expectedValues;
1193 for (const auto &test : condHead.bits) {
1194 absoluteBits.push_back(ResolveRegisterOperand(ArgumentType(test.bit),
1195 creg_map, "classical")
1196 .front());
1197 expectedValues.push_back(test.expected);
1198 }
1199
1201 elseBody.has_value(), "if");
1202 if (elseBody)
1203 RejectPredicateMutationBeforeReevaluation(*elseBody, absoluteBits,
1204 false, "else");
1205
1206 AddBitCondBranchExpr addBranch;
1207 std::vector<QoperationStatement> stmts =
1208 addBranch(body, absoluteBits, expectedValues);
1209
1210 if (elseBody) {
1211 // Negating a `&&` chain needs an or-of-negations, which
1212 // CreateEqualCondition cannot express.
1213 if (absoluteBits.size() > 1)
1214 throw std::invalid_argument(
1215 "'else' on a multi-bit condition (if (... && ...) { ... } else "
1216 "{ ... }) is not supported: negating a conjunction would require "
1217 "a disjunctive condition, and no such primitive exists.");
1218
1219 std::vector<bool> elseValues{!expectedValues.front()};
1220 std::vector<QoperationStatement> elseStmts =
1221 addBranch(*elseBody, absoluteBits, elseValues);
1222 stmts.insert(stmts.end(), elseStmts.begin(), elseStmts.end());
1223 }
1224
1225 return stmts;
1226 }
1227
1228 // Register-comparison form.
1229 if (elseBody)
1230 throw std::invalid_argument(
1231 "'else' on a register-comparison condition (if (" + condHead.regId +
1232 " == " + std::to_string(condHead.regValue) +
1233 ") { ... } else { ... }) is not supported: it would require a "
1234 "not-equal condition, and no such primitive exists. Only "
1235 "single-bit conditions (if (" +
1236 condHead.regId + "[i]) { ... } else { ... }) support 'else'.");
1237
1238 const std::vector<int> absoluteBits = ResolveRegisterOperand(
1239 ArgumentType(condHead.regId), creg_map, "classical");
1240 const std::vector<bool> expectedValues = DecodeRegisterCondition(
1241 condHead.regId, condHead.regValue, absoluteBits.size());
1242 RejectPredicateMutationBeforeReevaluation(body, absoluteBits, false, "if");
1243
1244 AddBitCondBranchExpr addBranch;
1245 return addBranch(body, absoluteBits, expectedValues);
1246 }
1247};
1248
1249inline phx::function<AddCondQopBracedExpr> AddCondQopBraced;
1250
1251struct Program {
1252 double version = 2.0;
1253 std::vector<StatementType> statements;
1254 std::vector<std::string> comments;
1255 std::vector<std::string> includes;
1256
1257 Program(const ProgramType &program = {}) {
1258 comments = boost::fusion::at_c<0>(program);
1259 version = boost::fusion::at_c<1>(program);
1260 includes = boost::fusion::at_c<2>(program);
1261 statements = boost::fusion::at_c<3>(program);
1262 }
1263
1264 void clear() {
1265 comments.clear();
1266 includes.clear();
1267 statements.clear();
1268 version = 2.0;
1269 }
1270
1271 template <typename Time = Types::time_type>
1272 std::shared_ptr<Circuits::Circuit<Time>> ToCircuit(
1273 std::unordered_map<std::string, StatementType> &opaqueGates,
1274 std::unordered_map<std::string, StatementType> &definedGates) const {
1275 auto circuit = std::make_shared<Circuits::Circuit<Time>>();
1276
1277 for (const auto &stmt : statements)
1278 AddToCircuit(circuit, stmt, opaqueGates, definedGates);
1279
1280 return circuit;
1281 }
1282
1283 template <typename Time = Types::time_type>
1284 static void AddToCircuit(
1285 const std::shared_ptr<Circuits::Circuit<Time>> &circuit,
1286 const StatementType &stmt,
1287 std::unordered_map<std::string, StatementType> &opaqueGates,
1288 std::unordered_map<std::string, StatementType> &definedGates) {
1289 switch (stmt.opType) {
1291 if (stmt.qubits.size() != stmt.cbits.size())
1292 throw std::invalid_argument(
1293 "Measurement operation: number of qubits "
1294 "and classical bits do not match.");
1295
1296 std::vector<std::pair<Types::qubit_t, size_t>> qs;
1297 for (size_t i = 0; i < stmt.qubits.size(); ++i)
1298 qs.push_back({static_cast<Types::qubit_t>(stmt.qubits[i]),
1299 static_cast<size_t>(stmt.cbits[i])});
1300
1301 auto measureOp =
1302 std::make_shared<Circuits::MeasurementOperation<Time>>(qs);
1303
1304 if (stmt.condBits.empty()) {
1305 circuit->AddOperation(measureOp);
1306 break;
1307 }
1308
1309 circuit->AddOperation(
1311 measureOp,
1313 } break;
1315 if (!stmt.condBits.empty())
1316 throw std::invalid_argument(
1317 "Conditional 'reset' (if ( ... ) reset q[i];) is not supported: "
1318 "the circuit representation has no conditional reset operation.");
1319
1320 Types::qubits_vector qubits(stmt.qubits.begin(), stmt.qubits.end());
1321 auto resetOp = Circuits::CircuitFactory<Time>::CreateReset(qubits);
1322 circuit->AddOperation(resetOp);
1323 } break;
1324
1326 double duration = stmt.parameters.empty() ? 0.0 : stmt.parameters[0];
1327 for (auto q : stmt.qubits) {
1328 circuit->AddOperation(Circuits::CircuitFactory<Time>::CreateDelay(
1329 static_cast<Types::qubit_t>(q), static_cast<Time>(duration)));
1330 }
1331 } break;
1332
1335 stmt.qubitsDecl.empty()) {
1336 // Identity gate ("id") or unrecognised no-op — skip silently.
1337 break;
1338 }
1340 // can add more than one gate here depending on what's in qubits
1341 double param1 = stmt.parameters.size() > 0 ? stmt.parameters[0] : 0;
1342 double param2 = stmt.parameters.size() > 1 ? stmt.parameters[1] : 0;
1343 double param3 = stmt.parameters.size() > 2 ? stmt.parameters[2] : 0;
1344 double param4 = stmt.parameters.size() > 3 ? stmt.parameters[3] : 0;
1345
1346 int nrQubits = AddGateExpr::GateNrQubits(stmt.gateType);
1347 if (stmt.qubits.size() % nrQubits != 0)
1348 throw std::invalid_argument(
1349 "Uop operation: number of qubits does "
1350 "not match the gate requirements.");
1351
1352 for (int pos = 0; pos < static_cast<int>(stmt.qubits.size());
1353 pos += nrQubits) {
1354 Types::qubits_vector gateQubits;
1355 for (int q = 0; q < nrQubits; ++q)
1356 gateQubits.push_back(
1357 static_cast<Types::qubit_t>(stmt.qubits[pos + q]));
1358
1360 stmt.gateType, gateQubits[0], nrQubits > 1 ? gateQubits[1] : 0,
1361 nrQubits > 2 ? gateQubits[2] : 0, param1, param2, param3,
1362 param4);
1363
1364 circuit->AddOperation(gateOp);
1365 }
1366 } else {
1367 // it's a defined gate, check further and implement
1368 // will add several gates to the circuit
1369 if (stmt.paramsDecl.size() != stmt.parameters.size())
1370 throw std::invalid_argument(
1371 "Uop operation: number of parameters do "
1372 "not match the declaration.");
1373
1374 std::unordered_map<std::string, double> variables;
1375
1376 for (size_t i = 0; i < stmt.paramsDecl.size(); ++i)
1377 variables[stmt.paramsDecl[i]] = stmt.parameters[i];
1378
1379 int nrQubits = static_cast<int>(stmt.qubitsDecl.size());
1380 if (stmt.qubits.size() % nrQubits != 0)
1381 throw std::invalid_argument(
1382 "Defined Uop operation: number of qubits "
1383 "does not match the gate requirements.");
1384
1385 for (int pos = 0; pos < static_cast<int>(stmt.qubits.size());
1386 pos += nrQubits) {
1387 std::unordered_map<std::string, IndexedId> qubitMap;
1388 for (int q = 0; q < nrQubits; ++q) {
1389 IndexedId id;
1390 id.id = stmt.qubitsDecl[q];
1391 id.base = stmt.qubits[pos + q];
1392 id.index = 1;
1393 qubitMap[id.id] = id;
1394 }
1395
1396 // now walk over all gates in declOps and add them to the circuit
1397 AddGateExpr addGate;
1398 for (const auto &op : stmt.declOps) {
1399 StatementType gateStmt =
1400 addGate(op, qubitMap, opaqueGates, definedGates, variables);
1401
1402 AddToCircuit(circuit, gateStmt, opaqueGates, definedGates);
1403 }
1404 }
1405 }
1406 } break;
1408 // Same guard as the Uop case above: without it a no-op statement -
1409 // "id", or a modifier that lowers to nothing such as `pow(0) @` or
1410 // `ctrl @ id` - reaches the defined-gate branch with an empty
1411 // qubitsDecl and evaluates 0 % 0, which raises SIGFPE and cannot be
1412 // caught and reported as a parse error.
1414 stmt.qubitsDecl.empty())
1415 break;
1416
1418 // can add more than one gate here depending on what's in qubits
1419 double param1 = stmt.parameters.size() > 0 ? stmt.parameters[0] : 0;
1420 double param2 = stmt.parameters.size() > 1 ? stmt.parameters[1] : 0;
1421 double param3 = stmt.parameters.size() > 2 ? stmt.parameters[2] : 0;
1422 double param4 = stmt.parameters.size() > 3 ? stmt.parameters[3] : 0;
1423
1424 int nrQubits = AddGateExpr::GateNrQubits(stmt.gateType);
1425 if (stmt.qubits.size() % nrQubits != 0)
1426 throw std::invalid_argument(
1427 "Uop operation: number of qubits does "
1428 "not match the gate requirements.");
1429
1430 const auto condition =
1432
1433 for (int pos = 0; pos < static_cast<int>(stmt.qubits.size());
1434 pos += nrQubits) {
1435 Types::qubits_vector gateQubits;
1436 for (int q = 0; q < nrQubits; ++q)
1437 gateQubits.push_back(
1438 static_cast<Types::qubit_t>(stmt.qubits[pos + q]));
1439
1441 stmt.gateType, gateQubits[0], nrQubits > 1 ? gateQubits[1] : 0,
1442 nrQubits > 2 ? gateQubits[2] : 0, param1, param2, param3,
1443 param4);
1444
1446 gateOp, condition);
1447 circuit->AddOperation(condOp);
1448 }
1449 } else {
1450 // it's a defined gate, check further and implement
1451 // will add several gates to the circuit
1452 if (stmt.paramsDecl.size() != stmt.parameters.size())
1453 throw std::invalid_argument(
1454 "Uop operation: number of parameters do "
1455 "not match the declaration.");
1456
1457 std::unordered_map<std::string, double> variables;
1458
1459 for (size_t i = 0; i < stmt.paramsDecl.size(); ++i)
1460 variables[stmt.paramsDecl[i]] = stmt.parameters[i];
1461
1462 int nrQubits = static_cast<int>(stmt.qubitsDecl.size());
1463 if (stmt.qubits.size() % nrQubits != 0)
1464 throw std::invalid_argument(
1465 "Defined Uop operation: number of qubits "
1466 "does not match the gate requirements.");
1467
1468 for (int pos = 0; pos < static_cast<int>(stmt.qubits.size());
1469 pos += nrQubits) {
1470 std::unordered_map<std::string, IndexedId> qubitMap;
1471 for (int q = 0; q < nrQubits; ++q) {
1472 IndexedId id;
1473 id.id = stmt.qubitsDecl[q];
1474 id.base = stmt.qubits[pos + q];
1475 id.index = 1;
1476 qubitMap[id.id] = id;
1477 }
1478
1479 // now walk over all gates in declOps and add them to the circuit
1480 AddGateExpr addGate;
1481 for (const auto &op : stmt.declOps) {
1482 StatementType gateStmt =
1483 addGate(op, qubitMap, opaqueGates, definedGates, variables);
1484
1485 // make each of them conditioned on the original condition
1487 gateStmt.condExpected = stmt.condExpected;
1488 gateStmt.cbits = stmt.cbits;
1489
1490 AddToCircuit(circuit, gateStmt, opaqueGates, definedGates);
1491 }
1492 }
1493 }
1494 } break;
1497 // Barriers are validated during parsing but intentionally erased here
1498 // because the Circuit IR has no barrier operation.
1501 case QoperationStatement::OperationType::
1502 GateDecl: // do not generate anything here, it's already handled when
1503 // the gate is called
1504 default:
1505 // those are ignored
1506 break;
1507 }
1508 }
1509};
1510
1511} // namespace qasm
1512
1513#endif // !_SYNTAXTREE_H_
static const std::shared_ptr< IQuantumGate< Time > > CreateGate(QuantumGateType type, size_t q1, size_t q2=0, size_t q3=0, double param1=0, double param2=0, double param3=0, double param4=0)
Construct a quantum gate.
static std::shared_ptr< IOperation< Time > > CreateConditionalGate(const std::shared_ptr< IGateOperation< Time > > &operation, const std::shared_ptr< ICondition > &condition)
Construct a conditional gate.
static std::shared_ptr< IOperation< Time > > CreateReset(const Types::qubits_vector &qubits={}, const std::vector< bool > &resetTgts={})
Construct a reset operation.
static std::shared_ptr< IOperation< Time > > CreateDelay(Types::qubit_t qubit=0, Time duration=0)
Construct a delay operation.
static std::shared_ptr< IOperation< Time > > CreateConditionalMeasurement(const std::shared_ptr< MeasurementOperation< Time > > &measurement, const std::shared_ptr< ICondition > &condition)
Constructs a conditional measurement.
static std::shared_ptr< ICondition > CreateEqualCondition(const std::vector< size_t > &ind, const std::vector< bool > &b)
Construct an equality condition.
Circuit class for holding the sequence of operations.
Definition Circuit.h:48
double Eval() const override
Definition Expr.h:374
std::string id
Definition SimpleOps.h:40
QuantumGateType
The type of quantum gates.
std::vector< qubit_t > qubits_vector
The type of a vector of qubits.
Definition Types.h:22
uint_fast64_t qubit_t
The type of a qubit.
Definition Types.h:21
phx::function< AddCondQopExpr > AddCondQop
std::variant< UGateCallType, CXGateCallType, GatecallType > UopType
Definition SimpleOps.h:167
boost::fusion::vector< std::string, MixedListType > SimpleGatecallType
Definition SimpleOps.h:158
phx::function< AddCondQopBracedExpr > AddCondQopBraced
phx::function< AddModifiedGateExpr > AddModifiedGate
Definition SyntaxTree.h:940
std::vector< ArgumentType > MixedListType
Definition SimpleOps.h:113
std::vector< bool > DecodeRegisterCondition(const std::string &registerName, int value, size_t width)
double EvaluateGateParameter(const Expression &parameter, const std::unordered_map< std::string, double > &variables)
Definition SyntaxTree.h:24
std::vector< ModifierType > ModifierListType
Definition SimpleOps.h:192
std::variant< SimpleGatecallType, ExpGatecallType > GatecallType
Definition SimpleOps.h:161
std::shared_ptr< Circuits::ICondition > MakeEqualCondition(const std::vector< int > &conditionBits, const std::vector< bool > &expectedValues)
boost::fusion::vector< std::vector< std::string >, double, std::vector< std::string >, std::vector< StatementType > > ProgramType
Definition SimpleOps.h:296
void ApplyCondition(StatementType &stmt, const std::vector< int > &conditionBits, const std::vector< bool > &expectedValues)
std::string regId
Definition SimpleOps.h:338
void RejectPredicateMutationBeforeReevaluation(const std::vector< QopType > &body, const std::vector< int > &conditionBits, bool followedByElse, std::string_view branchName)
std::vector< CondBitTest > bits
Definition SimpleOps.h:341
boost::fusion::vector< std::string, std::vector< Expression >, MixedListType > ExpGatecallType
Definition SimpleOps.h:159
boost::fusion::vector< std::string, int, QopType > CondOpType
Definition SimpleOps.h:309
phx::function< AddGateExpr > AddGate
Definition SyntaxTree.h:543
phx::function< RejectUnsupportedConstructExpr > RejectUnsupportedConstruct
Definition SyntaxTree.h:980
boost::fusion::vector< ModifierListType, UopType > ModifiedUopType
Definition SimpleOps.h:193
phx::function< RejectGateBodyModifierExpr > RejectGateBodyModifier
Definition SyntaxTree.h:960
std::variant< std::string, IndexedId > ArgumentType
Definition SimpleOps.h:111
phx::function< RejectQasm3OnlyGateExpr > RejectQasm3OnlyGate
QoperationStatement StatementType
Definition SimpleOps.h:294
std::vector< int > ResolveRegisterOperand(const ArgumentType &argument, const RegisterMap &registers, std::string_view kind)
Definition SimpleOps.h:117
std::vector< QoperationStatement > type
std::vector< QoperationStatement > operator()(const std::vector< QopType > &body, const std::vector< int > &absoluteBits, const std::vector< bool > &expectedValues) const
std::vector< QoperationStatement > operator()(const CondHeadType &condHead, const std::vector< QopType > &body, const boost::optional< std::vector< QopType > > &elseBody, const std::unordered_map< std::string, IndexedId > &qreg_map, const std::unordered_map< std::string, IndexedId > &creg_map, const std::unordered_map< std::string, StatementType > &opaqueGates, const std::unordered_map< std::string, StatementType > &definedGates) const
std::vector< QoperationStatement > type
QoperationStatement type
QoperationStatement operator()(CondOpType &condOp, const std::unordered_map< std::string, IndexedId > &qreg_map, const std::unordered_map< std::string, IndexedId > &creg_map, const std::unordered_map< std::string, StatementType > &opaqueGates, const std::unordered_map< std::string, StatementType > &definedGates) const
static bool IsSuppportedGate(const std::string &gateName)
Definition SyntaxTree.h:405
static int GateNrQubits(Circuits::QuantumGateType gateType)
Definition SyntaxTree.h:491
static const std::unordered_set< std::string > & AllowedMultipleParamsGates()
Definition SyntaxTree.h:517
static std::vector< int > ParseQubits(const ArgumentType &arg, const std::unordered_map< std::string, IndexedId > &qreg_map)
Definition SyntaxTree.h:386
QoperationStatement type
Definition SyntaxTree.h:36
static const std::unordered_set< std::string > & AllowedOneParamGates()
Definition SyntaxTree.h:513
static bool IsSuppportedOneParamGate(const std::string &gateName)
Definition SyntaxTree.h:396
static const std::unordered_set< std::string > & AllowedNoParamGates()
Definition SyntaxTree.h:509
static bool IsSuppportedMultipleParamsGate(const std::string &gateName)
Definition SyntaxTree.h:400
static bool IsSuppportedNoParamGate(const std::string &gateName)
Definition SyntaxTree.h:392
QoperationStatement operator()(const UopType &uop, const std::unordered_map< std::string, IndexedId > &qreg_map, const std::unordered_map< std::string, StatementType > &opaqueGates, const std::unordered_map< std::string, StatementType > &definedGates, const std::unordered_map< std::string, double > &variables={}) const
Definition SyntaxTree.h:39
Circuits::QuantumGateType GetGateType(const std::string &gateName) const
Definition SyntaxTree.h:417
static QoperationStatement ConjugateNegatedControls(const QoperationStatement &gate, const std::string &gateName, const std::vector< double > &params, const std::vector< size_t > &negatedControls)
Definition SyntaxTree.h:686
QoperationStatement operator()(const ModifiedUopType &modifiedUop, const std::unordered_map< std::string, IndexedId > &qreg_map, const std::unordered_map< std::string, StatementType > &opaqueGates, const std::unordered_map< std::string, StatementType > &definedGates, const std::unordered_map< std::string, double > &variables={}) const
Definition SyntaxTree.h:556
static void ApplyCtrl(std::string &gateName, std::vector< double > &params)
Definition SyntaxTree.h:881
static bool IsControl(const ModifierType &modifier)
Definition SyntaxTree.h:925
static void Canonicalize(const UopType &uop, std::string &gateName, std::vector< double > &params, MixedListType &args, const std::unordered_map< std::string, double > &variables)
Definition SyntaxTree.h:729
static void ApplyPow(double exponent, std::string &gateName, std::vector< double > &params, int &repetitions)
Definition SyntaxTree.h:835
static void ApplyInv(std::string &gateName, std::vector< double > &params)
Definition SyntaxTree.h:800
static QoperationStatement NoOpStatement()
Definition SyntaxTree.h:792
static void NormalizeGateName(std::string &gateName)
Definition SyntaxTree.h:766
static std::vector< size_t > NegatedControls(const ModifierListType &modifiers)
Definition SyntaxTree.h:664
static UopType MakeCall(const std::string &gateName, const std::vector< double > &params, const MixedListType &args)
Definition SyntaxTree.h:779
ModifierKind kind
Definition SimpleOps.h:178
std::shared_ptr< Circuits::Circuit< Time > > ToCircuit(std::unordered_map< std::string, StatementType > &opaqueGates, std::unordered_map< std::string, StatementType > &definedGates) const
std::vector< StatementType > statements
static void AddToCircuit(const std::shared_ptr< Circuits::Circuit< Time > > &circuit, const StatementType &stmt, std::unordered_map< std::string, StatementType > &opaqueGates, std::unordered_map< std::string, StatementType > &definedGates)
std::vector< std::string > comments
Program(const ProgramType &program={})
std::vector< std::string > includes
std::vector< UopType > declOps
Definition SimpleOps.h:291
std::vector< bool > condExpected
Definition SimpleOps.h:288
std::vector< int > condBits
Definition SimpleOps.h:290
std::vector< std::string > qubitsDecl
Definition SimpleOps.h:284
std::vector< double > parameters
Definition SimpleOps.h:281
std::vector< int > cbits
Definition SimpleOps.h:279
std::vector< std::string > paramsDecl
Definition SimpleOps.h:283
std::vector< int > qubits
Definition SimpleOps.h:278
Circuits::QuantumGateType gateType
Definition SimpleOps.h:272
UopType operator()(const ModifierListType &) const
Definition SyntaxTree.h:953
bool operator()(const std::string &gateName, const std::unordered_map< std::string, StatementType > &definedGates) const
QoperationStatement operator()(const std::string &message) const
Definition SyntaxTree.h:975