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