Maestro 0.1.0
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
TensorNetwork.h
Go to the documentation of this file.
1
12
13#pragma once
14
15#ifndef __TENSOR_NETWORK_H_
16#define __TENSOR_NETWORK_H_ 1
17
18#include <Eigen/Eigen>
19#include <unordered_set>
20#include <vector>
21
22#include "TensorContractor.h"
23#include "TensorNode.h"
24
25namespace TensorNetworks {
26
28public:
29 using Index = Eigen::Index;
30
31 TensorNetwork() = delete;
32
38 TensorNetwork(size_t numQubits)
39 : rng(std::random_device{}()), uniformZeroOne(0, 1) {
40 lastTensors.resize(numQubits);
41 lastTensorsSuper.resize(numQubits);
42 lastTensorIndices.resize(numQubits);
43 lastTensorIndicesSuper.resize(numQubits);
44
45 qubitsMap.resize(numQubits);
46
47 Clean(numQubits);
48 }
49
50 void Clear() {
51 // cannot do this if the one qubit gates are contracted into qubit
52 // tensors!!!!!!
53 /*
54 tensors.resize(2 * lastTensors.size()); // keep only the qubit tensors (both
55 normal and super)
56
57 qubitsGroups.clear();
58
59 SetQubitsTensorsClear(lastTensors.size());
60 */
61 tensors.clear();
62 qubitsGroups.clear();
63 Clean(GetNumQubits());
64 }
65
66 void
67 AddGate(const QC::Gates::QuantumGateWithOp<TensorNode::MatrixClass> &gate,
68 Types::qubit_t q1, Types::qubit_t q2 = 0) {
69 const auto gateQubitsNumber = gate.getQubitsNumber();
70 if (gateQubitsNumber > 1)
71 AddTwoQubitsGate(gate, q1, q2);
72 else
73 AddOneQubitGate(gate, q1);
74 }
75
76 double Probability(Types::qubit_t qubit, bool zero = true) {
77 if (!contractor)
78 return 0;
79 contractor->SetMultithreading(enableMultithreading);
80
81 // save the qubit positions, to be able to restore them back after the
82 // addition of the projector and the contraction
83 const auto lastTensorIdOnQubit = lastTensors[qubit];
84 const auto lastTensorIndexOnQubit = lastTensorIndices[qubit];
85
86 // add the projection tensor to the network
87 AddProjector(qubit, zero);
88
89 Connect();
90
91 // contract the network, save result to return
92 const double result = contractor->Contract(*this, qubit);
93
94 // restore back the network to the previous state (disconnect the connection
95 // to super tensors and remove the added projector)
96
97 lastTensors[qubit] = lastTensorIdOnQubit;
98 lastTensorIndices[qubit] = lastTensorIndexOnQubit;
99
100 // get rid of the added projection tensor
101 tensors.resize(tensors.size() - 1);
102
103 // no need to actually disconnect the indices, as they are not used unless
104 // they are connected again when adding a new gate/projection for
105 // measurement, they are reconnected anyway, the same goes for doing another
106 // contraction
107
108 // return the result
109 return result;
110 }
111
123 double ExpectationValue(const std::string &pauliString) {
124 if (pauliString.empty())
125 return 1.;
126 else if (!contractor)
127 return 0.;
128
129 auto savedTensorsNrLocal = tensors.size();
130 auto saveLastTensorsLocal = lastTensors;
131 auto saveLastTensorIndicesLocal = lastTensorIndices;
132
133 // add the gates from the Pauli string
134 const QC::Gates::PauliXGate<TensorNode::MatrixClass> XGate;
135 const QC::Gates::PauliYGate<TensorNode::MatrixClass> YGate;
136 const QC::Gates::PauliZGate<TensorNode::MatrixClass> ZGate;
137
138 std::unordered_set<Types::qubit_t> usedQubits;
139
140 for (Types::qubit_t q = 0; q < pauliString.size(); ++q) {
141 const char op = toupper(pauliString[q]);
142 switch (op) {
143 case 'X':
144 AddOneQubitExpectationValueOp(XGate, q);
145 usedQubits.insert(q);
146 break;
147 case 'Y':
148 AddOneQubitExpectationValueOp(YGate, q);
149 usedQubits.insert(q);
150 break;
151 case 'Z':
152 AddOneQubitExpectationValueOp(ZGate, q);
153 usedQubits.insert(q);
154 break;
155 case 'I':
156 [[fallthrough]];
157 default:
158 break;
159 }
160 }
161
162 if (usedQubits.empty())
163 return 1.;
164
165 const double result = Contract(usedQubits);
166
167 tensors.resize(savedTensorsNrLocal);
168 lastTensors.swap(saveLastTensorsLocal);
169 lastTensorIndices.swap(saveLastTensorIndicesLocal);
170
171 Disconnect();
172
173 return result;
174 }
175
176 double getBasisStateProbability(size_t outcome) {
178
179 size_t mask = 1ULL;
180
181 for (Types::qubit_t q = 0; q < GetNumQubits(); ++q) {
182 const bool expected = (outcome & mask) == 0;
183
184 AddProjector(q, expected);
185
186 mask <<= 1;
187 }
188
189 const double prob = Contract();
190
192
193 return prob;
194 }
195
197 const double p0 = Probability(qubit, false);
198 const double prob = uniformZeroOne(rng);
199 if (prob < p0) {
200 AddProjectorOp(qubit, false, p0);
201
202 return true;
203 }
204
205 AddProjectorOp(qubit, true, 1. - p0);
206
207 return false;
208 }
209
210 void AddProjector(Types::qubit_t qubit, bool zero = true) {
211 // add a projector tensor for the qubit, either zero or one
212 auto tensorNode = std::make_shared<TensorNode>();
213 tensorNode->SetProjector(qubit, zero);
214
215 const auto newTensorId = static_cast<Index>(tensors.size());
216 tensorNode->SetId(newTensorId);
217
218 // connect the tensor to the last tensors on the qubits
219
220 // first, tensors from the network are connected to the new tensor
221 // that is, their output indices are connected to the input indices of the
222 // new tensor second, the new tensor is connected to the tensors from the
223 // network
224
225 const auto tensorOnQubitId = lastTensors[qubit];
226 const auto indexOnQubit = lastTensorIndices[qubit];
227
228 // connect the last tensor on qubit with the projection tensor
229 const auto &lastTensor = tensors[tensorOnQubitId];
230 lastTensor->connections[indexOnQubit] = newTensorId;
231 lastTensor->connectionsIndices[indexOnQubit] = 0;
232
233 // also do the connection between the projection tensor back to the last
234 // tensor
235 tensorNode->connections[0] = tensorOnQubitId;
236 tensorNode->connectionsIndices[0] = indexOnQubit;
237
238 // add the tensor to the network
239 tensors.emplace_back(std::move(tensorNode));
240
241 // and set the last tensor on the qubit to be the new tensor
242 lastTensors[qubit] = newTensorId;
243 lastTensorIndices[qubit] =
244 1; // the next index is 1, this is going to be the 'connect' one
245 }
246
247 void AddProjectorOp(Types::qubit_t qubit, bool zero = true,
248 double prob = 1.) {
249 TensorNode::MatrixClass projMat = TensorNode::MatrixClass::Zero(2, 2);
250
251 const double renorm = 1. / sqrt(prob);
252 if (zero)
253 projMat(0, 0) = renorm;
254 else
255 projMat(1, 1) = renorm;
256
257 const QC::Gates::SingleQubitGate<TensorNode::MatrixClass> projOp(projMat);
258
259 AddGate(projOp, qubit);
260 }
261
262 size_t GetNumQubits() const { return lastTensors.size(); }
263
264 void SetContractor(const std::shared_ptr<ITensorContractor> &c) {
265 contractor = c;
266 }
267
268 std::shared_ptr<ITensorContractor> GetContractor() const {
269 return contractor;
270 }
271
272 const std::vector<std::shared_ptr<TensorNode>> &GetTensors() const {
273 return tensors;
274 }
275
276 const std::unordered_set<Types::qubit_t> &
278 return qubitsGroups.at(qubitsMap[q]);
279 }
280
281 void SaveState() {
282 saveTensors.resize(tensors.size());
283 for (size_t i = 0; i < tensors.size(); ++i)
284 saveTensors[i] = tensors[i]->CloneWithoutTensorCopy();
285
286 saveQubitsMap = qubitsMap;
287 saveQubitsGroups = qubitsGroups;
288
290 }
291
293 savedTensorsNr = tensors.size();
294 saveLastTensors = lastTensors;
295 saveLastTensorsSuper = lastTensorsSuper;
296 saveLastTensorIndices = lastTensorIndices;
297 saveLastTensorIndicesSuper = lastTensorIndicesSuper;
298 }
299
302
303 tensors.resize(saveTensors.size());
304 for (size_t i = 0; i < tensors.size(); ++i)
305 tensors[i] = saveTensors[i]->CloneWithoutTensorCopy();
306
307 qubitsMap = saveQubitsMap;
308 qubitsGroups = saveQubitsGroups;
309 }
310
312 tensors.resize(savedTensorsNr);
313 lastTensors = saveLastTensors;
314 lastTensorsSuper = saveLastTensorsSuper;
315 lastTensorIndices = saveLastTensorIndices;
316 lastTensorIndicesSuper = saveLastTensorIndicesSuper;
317 }
318
320 tensors.swap(saveTensors);
321
322 qubitsMap.swap(saveQubitsMap);
323 qubitsGroups.swap(saveQubitsGroups);
324
326
328 }
329
331 tensors.resize(savedTensorsNr);
332 savedTensorsNr = 0;
333 lastTensors.swap(saveLastTensors);
334 lastTensorsSuper.swap(saveLastTensorsSuper);
335 lastTensorIndices.swap(saveLastTensorIndices);
336 lastTensorIndicesSuper.swap(saveLastTensorIndicesSuper);
337
339 }
340
342 saveTensors.clear();
343
344 saveQubitsMap.clear();
345 saveQubitsGroups.clear();
346
348 }
349
351 saveLastTensors.clear();
352 saveLastTensorsSuper.clear();
353
354 saveLastTensorIndices.clear();
355 saveLastTensorIndicesSuper.clear();
356 }
357
358 void Connect() {
359 // connect all last tensors to the corresponding super tensors
360 for (Types::qubit_t q = 0; q < GetNumQubits(); ++q) {
361 const auto lastTensorId = lastTensors[q];
362 const auto lastTensorSuperId = lastTensorsSuper[q];
363 const auto &lastTensor = tensors[lastTensorId];
364 const auto &lastTensorSuper = tensors[lastTensorSuperId];
365
366 const auto tensorIndexOnQubit = lastTensorIndices[q];
367 const auto tensorSuperIndexOnQubit = lastTensorIndicesSuper[q];
368
369 // tensor connection to the super one
370 lastTensor->connections[tensorIndexOnQubit] = lastTensorSuperId;
371 lastTensor->connectionsIndices[tensorIndexOnQubit] =
372 tensorSuperIndexOnQubit;
373
374 // the super tensor connected to the left tensor
375 lastTensorSuper->connections[tensorSuperIndexOnQubit] = lastTensorId;
376 lastTensorSuper->connectionsIndices[tensorSuperIndexOnQubit] =
377 tensorIndexOnQubit;
378 }
379 }
380
381 void Disconnect() {
382 for (Types::qubit_t q = 0; q < GetNumQubits(); ++q) {
383 const auto lastTensorId = lastTensors[q];
384 const auto lastTensorSuperId = lastTensorsSuper[q];
385 const auto &lastTensor = tensors[lastTensorId];
386 const auto &lastTensorSuper = tensors[lastTensorSuperId];
387
388 const auto tensorIndexOnQubit = lastTensorIndices[q];
389 const auto tensorSuperIndexOnQubit = lastTensorIndicesSuper[q];
390
391 // tensor connection to the super one
392 lastTensor->connections[tensorIndexOnQubit] = TensorNode::NotConnected;
393 lastTensor->connectionsIndices[tensorIndexOnQubit] =
395
396 // the super tensor connected to the left tensor
397 lastTensorSuper->connections[tensorSuperIndexOnQubit] =
399 lastTensorSuper->connectionsIndices[tensorSuperIndexOnQubit] =
401 }
402 }
403
412 void SetMultithreading(bool multithreading = true) {
413 enableMultithreading = multithreading;
414 }
415
423 bool GetMultithreading() const { return enableMultithreading; }
424
425 std::unique_ptr<TensorNetwork> Clone() const {
426 auto cloned = std::make_unique<TensorNetwork>(0);
427
428 cloned->tensors = tensors; // all tensors in the network
429
430 cloned->lastTensors =
431 lastTensors; // the indices of the last tensors in the network
432 cloned->lastTensorsSuper = lastTensorsSuper; // the indices of the last
433 // super tensors in the network
434
435 cloned->lastTensorIndices =
436 lastTensorIndices; // the indices of the last tensors in the network
437 cloned->lastTensorIndicesSuper =
438 lastTensorIndicesSuper; // the indices of the last super tensors in the
439 // network
440
441 cloned->qubitsMap = qubitsMap;
443 cloned->qubitsGroups = qubitsGroups;
445
446 cloned->savedTensorsNr = savedTensorsNr;
447 cloned->saveTensors = saveTensors; // all tensors in the network
448
449 cloned->saveLastTensors =
450 saveLastTensors; // the indices of the last tensors in the network
451 cloned->saveLastTensorsSuper =
452 saveLastTensorsSuper; // the indices of the last super tensors in the
453 // network
454
455 cloned->saveLastTensorIndices =
456 saveLastTensorIndices; // the indices of the last tensors in the network
457 cloned->saveLastTensorIndicesSuper =
458 saveLastTensorIndicesSuper; // the indices of the last super tensors in
459 // the network
460
461 cloned->saveQubitsMap =
462 saveQubitsMap;
464 cloned->saveQubitsGroups =
465 saveQubitsGroups;
467
468 if (contractor)
469 cloned->contractor = contractor->Clone();
470
471 return cloned;
472 }
473
474private:
475 void AddOneQubitExpectationValueOp(
476 const QC::Gates::QuantumGateWithOp<TensorNode::MatrixClass> &gate,
477 Types::qubit_t q) {
478 auto tensorNode = std::make_shared<TensorNode>();
479 tensorNode->SetGate(gate, q);
480
481 auto lastTensorId = lastTensors[q];
482 const auto &lastTensor = tensors[lastTensorId];
483
484 // can be either 0 if the last tensor is for a single qubit (the first
485 // tensor) or 1 if it's from a one qubit gate tensor or either 2 or 3 if it
486 // corresponds to a two qubit gate tensor for 0 or 1 do nothing to qubits or
487 // connections in the altered node, nothing changes for 3 also nothing
488 // should be done, as the qubit is the second one in the gate and
489 // contraction doesn't change anything but if it's 2, the order changes
490 auto lastTensorIndexForQubit = lastTensorIndices[q];
491
492 auto tensorId = static_cast<Index>(tensors.size());
493 tensorNode->SetId(tensorId);
494
495 // connect the tensor to the last tensors on the qubits
496
497 // first, tensors from the network are connected to the new tensor
498 // that is, their output indices are connected to the input indices of the
499 // new tensor second, the new tensor is connected to the tensors from the
500 // network
501
502 lastTensor->connections[lastTensorIndexForQubit] = tensorId;
503 lastTensor->connectionsIndices[lastTensorIndexForQubit] =
504 0; // connect it to the zero index of the new tensor
505
506 tensorNode->connections[0] = lastTensorId;
507 tensorNode->connectionsIndices[0] = lastTensorIndexForQubit;
508
509 tensors.emplace_back(std::move(tensorNode));
510
511 // now set the last tensors and indices
512 lastTensors[q] = tensorId;
513
514 lastTensorIndices[q] = 1;
515 }
516
517 void AddOneQubitGate(
518 const QC::Gates::QuantumGateWithOp<TensorNode::MatrixClass> &gate,
519 Types::qubit_t q, bool contractWithTwoQubitTensors = false,
520 bool contract = true) {
521 // instead of adding it to the network, simply contract it with the previous
522 // tensor on the qubit
523 auto tensorNode = std::make_shared<TensorNode>();
524 tensorNode->SetGate(gate, q);
525
526 auto lastTensorId = lastTensors[q];
527 const auto &lastTensor = tensors[lastTensorId];
528
529 // can be either 0 if the last tensor is for a single qubit (the first
530 // tensor) or 1 if it's from a one qubit gate tensor or either 2 or 3 if it
531 // corresponds to a two qubit gate tensor for 0 or 1 do nothing to qubits or
532 // connections in the altered node, nothing changes for 3 also nothing
533 // should be done, as the qubit is the second one in the gate and
534 // contraction doesn't change anything but if it's 2, the order changes
535 auto lastTensorIndexForQubit = lastTensorIndices[q];
536
537 // if contraction with two qubit tensors is desired, if it's on the last
538 // index it's easy if not, it's more complex because contraction changes the
539 // order of indices it's not easy to deal with that as the other qubit might
540 // have already another tensor connected/over it maybe using Shuffle is
541 // easier than changing connections and indices and qubits and so on...
542 if (contract &&
543 (contractWithTwoQubitTensors || lastTensor->qubits.size() <= 2)) {
544 // previous tensor is a one qubit tensor, contract it with the new one
545
546 // TODO: Should I also contract them if the previous tensor is a two qubit
547 // tensor? probably yes, but probably only when the 'last tensor' is a one
548 // qubit one... and it's hit with a two qubit tensor because contracting
549 // together the one qubit tensors is quite fast
550
551 // tensors, lastTensors, lastTensorsSuper do not need change (except the
552 // new tensor inside the tensorNode) in the node, the connections do not
553 // need to change, index 0 and 1 remain the same, the input ones and do
554 // not change order (so also the connected nodes are not affected)
555
556 lastTensor->tensor = std::make_shared<Utils::Tensor<>>(
557 std::move(lastTensor->tensor->Contract(*(tensorNode->tensor),
558 lastTensorIndexForQubit, 0,
559 enableMultithreading)));
560
561 // TODO: Instead of Shuffle try by swapping qubits and indices... but also
562 // take care of connections if on the other qubit there is already a gate
563 // added
564 static const std::vector<size_t> indices{0, 1, 3, 2};
565 if (lastTensorIndexForQubit == 2) {
566 // this works, but it's slow, so let's try something else
567 // lastTensor->tensor =
568 // std::make_shared<Utils::Tensor<>>(std::move(lastTensor->tensor->Shuffle(indices)));
569 const auto otherQubit = lastTensor->qubits[3];
570 const auto otherTensorId = lastTensors[otherQubit];
571
572 std::swap(lastTensor->qubits[2], lastTensor->qubits[3]);
573 std::swap(lastTensor->connections[2], lastTensor->connections[3]);
574 std::swap(lastTensor->connectionsIndices[2],
575 lastTensor->connectionsIndices[3]);
576
577 lastTensorIndices[q] = 3;
578
579 if (otherTensorId == lastTensorId)
580 lastTensorIndices[otherQubit] = 2;
581 else {
582 // another tensor went on top of the other qubit/leg, so update
583 // connections
584 const auto &otherTensor =
585 tensors[lastTensor->connections[2]]; // 3 was swapped with 2
586
587 const auto otherIndex =
588 lastTensor->connectionsIndices[2]; // 3 was swapped with 2
589 otherTensor->connectionsIndices[otherIndex] =
590 2; // 3 was swapped with 2
591 }
592 }
593
594 tensorNode->SetSuper();
595
596 lastTensorId = lastTensorsSuper[q];
597 const auto &lastTensorSuper = tensors[lastTensorId];
598 lastTensorIndexForQubit = lastTensorIndicesSuper[q];
599
600 lastTensorSuper->tensor = std::make_shared<Utils::Tensor<>>(std::move(
601 lastTensorSuper->tensor->Contract(*(tensorNode->tensor),
602 lastTensorIndexForQubit, 0,
603 enableMultithreading)));
604
605 if (lastTensorIndexForQubit == 2) {
606 // lastTensorSuper->tensor =
607 // std::make_shared<Utils::Tensor<>>(std::move(lastTensorSuper->tensor->Shuffle(indices)));
608 const auto otherQubit = lastTensorSuper->qubits[3];
609 const auto otherTensorId = lastTensorsSuper[otherQubit];
610
611 std::swap(lastTensorSuper->qubits[2], lastTensorSuper->qubits[3]);
612 std::swap(lastTensorSuper->connections[2],
613 lastTensorSuper->connections[3]);
614 std::swap(lastTensorSuper->connectionsIndices[2],
615 lastTensorSuper->connectionsIndices[3]);
616
617 lastTensorIndicesSuper[q] = 3;
618
619 if (otherTensorId == lastTensorId)
620 lastTensorIndicesSuper[otherQubit] = 2;
621 else {
622 // another tensor went on top of the other qubit/leg, so update
623 // connections
624 const auto &otherTensor =
625 tensors[lastTensorSuper->connections[2]]; // 3 was swapped with 2
626
627 const auto otherIndex =
628 lastTensorSuper->connectionsIndices[2]; // 3 was swapped with 2
629 otherTensor->connectionsIndices[otherIndex] =
630 2; // 3 was swapped with 2
631 }
632 }
633 } else {
634 auto tensorId = static_cast<Index>(tensors.size());
635 tensorNode->SetId(tensorId);
636
637 // connect the tensor to the last tensors on the qubits
638
639 // first, tensors from the network are connected to the new tensor
640 // that is, their output indices are connected to the input indices of the
641 // new tensor second, the new tensor is connected to the tensors from the
642 // network
643
644 lastTensor->connections[lastTensorIndexForQubit] = tensorId;
645 lastTensor->connectionsIndices[lastTensorIndexForQubit] =
646 0; // connect it to the zero index of the new tensor
647
648 tensorNode->connections[0] = lastTensorId;
649 tensorNode->connectionsIndices[0] = lastTensorIndexForQubit;
650
651 tensors.emplace_back(std::move(tensorNode));
652
653 // now set the last tensors and indices
654 lastTensors[q] = tensorId;
655
656 lastTensorIndices[q] = 1;
657
658 // add the 'super' now
659
660 tensorNode = std::make_shared<TensorNode>();
661
662 tensorNode->SetGate(gate, q);
663 tensorNode->SetSuper();
664
665 tensorId = static_cast<Index>(tensors.size());
666 tensorNode->SetId(tensorId);
667
668 // connect the tensor to the last tensors on the qubits
669
670 // first, tensors from the network are connected to the new tensor
671 // that is, their output indices are connected to the input indices of the
672 // new tensor second, the new tensor is connected to the tensors from the
673 // network
674
675 lastTensorId = lastTensorsSuper[q];
676 const auto &lastTensorSuper = tensors[lastTensorId];
677 lastTensorIndexForQubit = lastTensorIndicesSuper[q];
678 lastTensorSuper->connections[lastTensorIndexForQubit] = tensorId;
679 lastTensorSuper->connectionsIndices[lastTensorIndexForQubit] = 0;
680
681 tensorNode->connections[0] = lastTensorId;
682 tensorNode->connectionsIndices[0] = lastTensorIndexForQubit;
683
684 tensors.emplace_back(std::move(tensorNode));
685
686 // now set the last tensors and indices
687 lastTensorsSuper[q] = tensorId;
688
689 lastTensorIndicesSuper[q] = 1;
690 }
691 }
692
693 void AddTwoQubitsGate(
694 const QC::Gates::QuantumGateWithOp<TensorNode::MatrixClass> &gate,
696 const size_t q1group = qubitsMap[q1];
697 const size_t q2group = qubitsMap[q2];
698
699 if (q1group != q2group) {
700 // merge groups - move all qubits from the second group to the first one
701 for (const auto qg : qubitsGroups[q2group]) {
702 qubitsMap[qg] = q1group;
703 qubitsGroups[q1group].insert(qg);
704 }
705 qubitsGroups.erase(q2group); // remove the second group
706 }
707
708 // TODO: if first two qubits gate on the 1-qubit tensors of the qubits,
709 // contract them with this node????? or even if only one of them is on a
710 // 1-qubit tensor, contract it with this one????
711
712 auto tensorNode = std::make_shared<TensorNode>();
713 tensorNode->SetGate(gate, q1, q2);
714
715 auto tensorId = static_cast<Index>(tensors.size());
716 tensorNode->SetId(tensorId);
717
718 // connect the tensor to the last tensors on the qubits
719
720 // first, tensors from the network are connected to the new tensor
721 // that is, their output indices are connected to the input indices of the
722 // new tensor second, the new tensor is connected to the tensors from the
723 // network
724
725 auto lastTensorId = lastTensors[q1];
726
727 const auto &lastTensor = tensors[lastTensorId];
728 auto lastTensorIndexForQubit = lastTensorIndices[q1];
729 lastTensor->connections[lastTensorIndexForQubit] = tensorId;
730 lastTensor->connectionsIndices[lastTensorIndexForQubit] =
731 0; // connect it to the zero index of the new tensor
732
733 tensorNode->connections[0] = lastTensorId;
734 tensorNode->connectionsIndices[0] = lastTensorIndexForQubit;
735
736 lastTensorId = lastTensors[q2];
737 const auto &lastTensor2 = tensors[lastTensorId];
738 lastTensorIndexForQubit = lastTensorIndices[q2];
739 lastTensor2->connections[lastTensorIndexForQubit] = tensorId;
740 lastTensor2->connectionsIndices[lastTensorIndexForQubit] =
741 1; // connect it to the first index of the new tensor
742
743 tensorNode->connections[1] = lastTensorId;
744 tensorNode->connectionsIndices[1] = lastTensorIndices[q2];
745
746 tensors.emplace_back(std::move(tensorNode));
747
748 // now set the last tensors and indices
749 lastTensors[q1] = tensorId;
750 lastTensors[q2] = tensorId;
751
752 lastTensorIndices[q1] = 2;
753 lastTensorIndices[q2] = 3;
754
755 // add the 'super' now
756
757 tensorNode = std::make_shared<TensorNode>();
758
759 tensorNode->SetGate(gate, q1, q2);
760 tensorNode->SetSuper();
761
762 tensorId = static_cast<Index>(tensors.size());
763 tensorNode->SetId(tensorId);
764
765 // connect the tensor to the last tensors on the qubits
766
767 // first, tensors from the network are connected to the new tensor
768 // that is, their output indices are connected to the input indices of the
769 // new tensor second, the new tensor is connected to the tensors from the
770 // network
771
772 lastTensorId = lastTensorsSuper[q1];
773 const auto &lastTensorSuper = tensors[lastTensorId];
774 lastTensorIndexForQubit = lastTensorIndicesSuper[q1];
775 lastTensorSuper->connections[lastTensorIndexForQubit] = tensorId;
776 lastTensorSuper->connectionsIndices[lastTensorIndexForQubit] = 0;
777
778 tensorNode->connections[0] = lastTensorId;
779 tensorNode->connectionsIndices[0] = lastTensorIndexForQubit;
780
781 lastTensorId = lastTensorsSuper[q2];
782 const auto &lastTensor2Super = tensors[lastTensorId];
783 lastTensorIndexForQubit = lastTensorIndicesSuper[q2];
784 lastTensor2Super->connections[lastTensorIndexForQubit] = tensorId;
785 lastTensor2Super->connectionsIndices[lastTensorIndexForQubit] = 1;
786
787 tensorNode->connections[1] = lastTensorId;
788 tensorNode->connectionsIndices[1] = lastTensorIndicesSuper[q2];
789
790 tensors.emplace_back(std::move(tensorNode));
791
792 // now set the last tensors and indices
793 lastTensorsSuper[q1] = tensorId;
794 lastTensorsSuper[q2] = tensorId;
795
796 lastTensorIndicesSuper[q1] = 2;
797 lastTensorIndicesSuper[q2] = 3;
798 }
799
800 double Contract() {
801 if (!contractor)
802 return 0;
803 contractor->SetMultithreading(enableMultithreading);
804
805 Connect();
806
807 // contract the network, save result to return
808 double result = 1;
809
810 for (auto &group : qubitsGroups) {
811 const double groupResult = contractor->Contract(
812 *this,
813 *group.second.begin()); // doesn't really matter which qubit is passed
814 // as long it belongs to the group
815 if (groupResult == 0)
816 return 0;
817 result *= groupResult;
818 }
819
820 // no need to actually disconnect the indices, as they are not used unless
821 // they are connected again when adding a new gate/projection for
822 // measurement, they are reconnected anyway, the same goes for doing another
823 // contraction
824
825 // return the result
826 return result;
827 }
828
829 double Contract(const std::unordered_set<Types::qubit_t> &qubits) {
830 if (!contractor)
831 return 0;
832 contractor->SetMultithreading(enableMultithreading);
833
834 Connect();
835
836 // contract the network, save result to return
837 double result = 1;
838
839 for (auto &group : qubitsGroups) {
840 // this is probably less costly than contracting
841 for (const auto q : group.second)
842 if (qubits.find(q) != qubits.end()) {
843 // this group has at least one qubit in the set, contract it
844 // break to avoid contracting it multiple times
845 const double groupResult = contractor->Contract(
846 *this, q); // doesn't really matter which qubit is passed as long
847 // it belongs to the group
848 if (groupResult == 0)
849 return 0;
850 result *= groupResult;
851 break;
852 }
853 }
854
855 // no need to actually disconnect the indices, as they are not used unless
856 // they are connected again when adding a new gate/projection for
857 // measurement, they are reconnected anyway, the same goes for doing another
858 // contraction
859
860 // return the result
861 return result;
862 }
863
864 void Clean(size_t numQubits) { SetQubitsTensors(numQubits); }
865
866 void SetQubitsTensors(size_t numQubits) {
867 for (Types::qubit_t q = 0; q < numQubits; ++q) {
868 qubitsMap[q] = q; // the qubit group id is the qubit number itself
869 qubitsGroups[q].insert(
870 q); // the qubit group contains only the qubit itself
871
872 auto tensorNode = std::make_shared<TensorNode>();
873 tensorNode->SetQubit(q);
874 tensorNode->SetId(static_cast<Index>(tensors.size()));
875
876 lastTensors[q] = tensorNode->GetId();
877 lastTensorIndices[q] = 0;
878 tensors.emplace_back(std::move(tensorNode));
879
880 tensorNode = std::make_shared<TensorNode>();
881 tensorNode->SetQubit(q);
882 tensorNode->SetId(static_cast<Index>(tensors.size()));
883 tensorNode->SetSuper();
884
885 lastTensorsSuper[q] = tensorNode->GetId();
886 lastTensorIndicesSuper[q] = 0;
887 tensors.emplace_back(std::move(tensorNode));
888 }
889 }
890
891 void SetQubitsTensorsClear(size_t numQubits) {
892 for (Types::qubit_t q = 0; q < numQubits; ++q) {
893 qubitsMap[q] = q; // the qubit group id is the qubit number itself
894 qubitsGroups[q].insert(
895 q); // the qubit group contains only the qubit itself
896
897 lastTensors[q] = 2 * q;
898 lastTensorIndices[q] = 0;
899
900 lastTensorsSuper[q] = lastTensors[q] + 1;
901 lastTensorIndicesSuper[q] = 0;
902 }
903 }
904
905 std::vector<std::shared_ptr<TensorNode>>
906 tensors; // all tensors in the network
907
908 std::vector<Index>
909 lastTensors; // the indices of the last tensors in the network
910 std::vector<Index>
911 lastTensorsSuper; // the indices of the last super tensors in the network
912
913 std::vector<Index>
914 lastTensorIndices; // the indices of the last tensors in the network
915 std::vector<Index> lastTensorIndicesSuper; // the indices of the last super
916 // tensors in the network
917
918 std::vector<size_t> qubitsMap;
920 std::unordered_map<size_t, std::unordered_set<Types::qubit_t>>
921 qubitsGroups;
923
924 size_t savedTensorsNr = 0;
925 std::vector<std::shared_ptr<TensorNode>>
926 saveTensors; // all tensors in the network
927
928 std::vector<Index>
929 saveLastTensors; // the indices of the last tensors in the network
930 std::vector<Index> saveLastTensorsSuper; // the indices of the last super
931 // tensors in the network
932
933 std::vector<Index>
934 saveLastTensorIndices; // the indices of the last tensors in the network
935 std::vector<Index> saveLastTensorIndicesSuper; // the indices of the last
936 // super tensors in the network
937
938 std::vector<size_t>
939 saveQubitsMap;
941 std::unordered_map<size_t, std::unordered_set<Types::qubit_t>>
942 saveQubitsGroups;
944
945 std::shared_ptr<ITensorContractor> contractor;
946
947 bool enableMultithreading = true;
948
949 std::mt19937_64 rng;
950 std::uniform_real_distribution<double> uniformZeroOne;
951};
952
953} // namespace TensorNetworks
954
955#endif // __TENSOR_NETWORK_H_
bool Measure(Types::qubit_t qubit)
void AddProjector(Types::qubit_t qubit, bool zero=true)
double Probability(Types::qubit_t qubit, bool zero=true)
void SetMultithreading(bool multithreading=true)
Enable/disable multithreading.
bool GetMultithreading() const
Get the multithreading flag.
void AddGate(const QC::Gates::QuantumGateWithOp< TensorNode::MatrixClass > &gate, Types::qubit_t q1, Types::qubit_t q2=0)
double ExpectationValue(const std::string &pauliString)
Returns the expected value of a Pauli string.
void AddProjectorOp(Types::qubit_t qubit, bool zero=true, double prob=1.)
TensorNetwork(size_t numQubits)
Constructor.
const std::unordered_set< Types::qubit_t > & GetQubitGroup(Types::qubit_t q) const
std::unique_ptr< TensorNetwork > Clone() const
const std::vector< std::shared_ptr< TensorNode > > & GetTensors() const
std::shared_ptr< ITensorContractor > GetContractor() const
double getBasisStateProbability(size_t outcome)
void SetContractor(const std::shared_ptr< ITensorContractor > &c)
Eigen::MatrixXcd MatrixClass
Definition TensorNode.h:30
static constexpr Index NotConnected
Definition TensorNode.h:155
uint_fast64_t qubit_t
The type of a qubit.
Definition Types.h:20