Maestro 0.3.1
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
28 public:
29 void SetSeed(uint64_t seed) { rng.seed(seed); }
30 using Index = Eigen::Index;
31
32 TensorNetwork() = delete;
33
39 TensorNetwork(size_t numQubits)
40 : rng(std::random_device{}()), uniformZeroOne(0, 1) {
41 lastTensors.resize(numQubits);
42 lastTensorsSuper.resize(numQubits);
43 lastTensorIndices.resize(numQubits);
44 lastTensorIndicesSuper.resize(numQubits);
45
46 qubitsMap.resize(numQubits);
47
48 Clean(numQubits);
49 }
50
51 void Clear() {
52 // cannot do this if the one qubit gates are contracted into qubit
53 // tensors!!!!!!
54 /*
55 tensors.resize(2 * lastTensors.size()); // keep only the qubit tensors (both
56 normal and super)
57
58 qubitsGroups.clear();
59
60 SetQubitsTensorsClear(lastTensors.size());
61 */
62 tensors.clear();
63 qubitsGroups.clear();
64 Clean(GetNumQubits());
65 }
66
67 void AddGate(
68 const QC::Gates::QuantumGateWithOp<TensorNode::MatrixClass> &gate,
69 Types::qubit_t q1, Types::qubit_t q2 = 0) {
70 const auto gateQubitsNumber = gate.getQubitsNumber();
71 if (gateQubitsNumber > 1)
72 AddTwoQubitsGate(gate, q1, q2);
73 else
74 AddOneQubitGate(gate, q1);
75 }
76
77 double Probability(Types::qubit_t qubit, bool zero = true) {
78 if (!contractor) 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()) return 1.;
163
164 const double result = Contract(usedQubits);
165
166 tensors.resize(savedTensorsNrLocal);
167 lastTensors.swap(saveLastTensorsLocal);
168 lastTensorIndices.swap(saveLastTensorIndicesLocal);
169
170 Disconnect();
171
172 return result;
173 }
174
175 double getBasisStateProbability(size_t outcome) {
177
178 size_t mask = 1ULL;
179
180 for (Types::qubit_t q = 0; q < GetNumQubits(); ++q) {
181 const bool expected = (outcome & mask) == 0;
182
183 AddProjector(q, expected);
184
185 mask <<= 1;
186 }
187
188 const double prob = Contract();
189
191
192 return prob;
193 }
194
196 const double p0 = Probability(qubit, false);
197 const double prob = uniformZeroOne(rng);
198 if (prob < p0) {
199 AddProjectorOp(qubit, false, p0);
200
201 return true;
202 }
203
204 AddProjectorOp(qubit, true, 1. - p0);
205
206 return false;
207 }
208
209 void AddProjector(Types::qubit_t qubit, bool zero = true) {
210 // add a projector tensor for the qubit, either zero or one
211 auto tensorNode = std::make_shared<TensorNode>();
212 tensorNode->SetProjector(qubit, zero);
213
214 const auto newTensorId = static_cast<Index>(tensors.size());
215 tensorNode->SetId(newTensorId);
216
217 // connect the tensor to the last tensors on the qubits
218
219 // first, tensors from the network are connected to the new tensor
220 // that is, their output indices are connected to the input indices of the
221 // new tensor second, the new tensor is connected to the tensors from the
222 // network
223
224 const auto tensorOnQubitId = lastTensors[qubit];
225 const auto indexOnQubit = lastTensorIndices[qubit];
226
227 // connect the last tensor on qubit with the projection tensor
228 const auto &lastTensor = tensors[tensorOnQubitId];
229 lastTensor->connections[indexOnQubit] = newTensorId;
230 lastTensor->connectionsIndices[indexOnQubit] = 0;
231
232 // also do the connection between the projection tensor back to the last
233 // tensor
234 tensorNode->connections[0] = tensorOnQubitId;
235 tensorNode->connectionsIndices[0] = indexOnQubit;
236
237 // add the tensor to the network
238 tensors.emplace_back(std::move(tensorNode));
239
240 // and set the last tensor on the qubit to be the new tensor
241 lastTensors[qubit] = newTensorId;
242 lastTensorIndices[qubit] =
243 1; // the next index is 1, this is going to be the 'connect' one
244 }
245
246 void AddProjectorOp(Types::qubit_t qubit, bool zero = true,
247 double prob = 1.) {
248 TensorNode::MatrixClass projMat = TensorNode::MatrixClass::Zero(2, 2);
249
250 const double renorm = 1. / sqrt(prob);
251 if (zero)
252 projMat(0, 0) = renorm;
253 else
254 projMat(1, 1) = renorm;
255
256 const QC::Gates::SingleQubitGate<TensorNode::MatrixClass> projOp(projMat);
257
258 AddGate(projOp, qubit);
259 }
260
261 size_t GetNumQubits() const { return lastTensors.size(); }
262
263 void SetContractor(const std::shared_ptr<ITensorContractor> &c) {
264 contractor = c;
265 }
266
267 std::shared_ptr<ITensorContractor> GetContractor() const {
268 return contractor;
269 }
270
271 const std::vector<std::shared_ptr<TensorNode>> &GetTensors() const {
272 return tensors;
273 }
274
275 const std::unordered_set<Types::qubit_t> &GetQubitGroup(
276 Types::qubit_t q) const {
277 return qubitsGroups.at(qubitsMap[q]);
278 }
279
280 void SaveState() {
281 saveTensors.resize(tensors.size());
282 for (size_t i = 0; i < tensors.size(); ++i)
283 saveTensors[i] = tensors[i]->CloneWithoutTensorCopy();
284
285 saveQubitsMap = qubitsMap;
286 saveQubitsGroups = qubitsGroups;
287
289 }
290
292 savedTensorsNr = tensors.size();
293 saveLastTensors = lastTensors;
294 saveLastTensorsSuper = lastTensorsSuper;
295 saveLastTensorIndices = lastTensorIndices;
296 saveLastTensorIndicesSuper = lastTensorIndicesSuper;
297 }
298
301
302 tensors.resize(saveTensors.size());
303 for (size_t i = 0; i < tensors.size(); ++i)
304 tensors[i] = saveTensors[i]->CloneWithoutTensorCopy();
305
306 qubitsMap = saveQubitsMap;
307 qubitsGroups = saveQubitsGroups;
308 }
309
311 tensors.resize(savedTensorsNr);
312 lastTensors = saveLastTensors;
313 lastTensorsSuper = saveLastTensorsSuper;
314 lastTensorIndices = saveLastTensorIndices;
315 lastTensorIndicesSuper = saveLastTensorIndicesSuper;
316 }
317
319 tensors.swap(saveTensors);
320
321 qubitsMap.swap(saveQubitsMap);
322 qubitsGroups.swap(saveQubitsGroups);
323
325
327 }
328
330 tensors.resize(savedTensorsNr);
331 savedTensorsNr = 0;
332 lastTensors.swap(saveLastTensors);
333 lastTensorsSuper.swap(saveLastTensorsSuper);
334 lastTensorIndices.swap(saveLastTensorIndices);
335 lastTensorIndicesSuper.swap(saveLastTensorIndicesSuper);
336
338 }
339
341 saveTensors.clear();
342
343 saveQubitsMap.clear();
344 saveQubitsGroups.clear();
345
347 }
348
350 saveLastTensors.clear();
351 saveLastTensorsSuper.clear();
352
353 saveLastTensorIndices.clear();
354 saveLastTensorIndicesSuper.clear();
355 }
356
357 void Connect() {
358 // connect all last tensors to the corresponding super tensors
359 for (Types::qubit_t q = 0; q < GetNumQubits(); ++q) {
360 const auto lastTensorId = lastTensors[q];
361 const auto lastTensorSuperId = lastTensorsSuper[q];
362 const auto &lastTensor = tensors[lastTensorId];
363 const auto &lastTensorSuper = tensors[lastTensorSuperId];
364
365 const auto tensorIndexOnQubit = lastTensorIndices[q];
366 const auto tensorSuperIndexOnQubit = lastTensorIndicesSuper[q];
367
368 // tensor connection to the super one
369 lastTensor->connections[tensorIndexOnQubit] = lastTensorSuperId;
370 lastTensor->connectionsIndices[tensorIndexOnQubit] =
371 tensorSuperIndexOnQubit;
372
373 // the super tensor connected to the left tensor
374 lastTensorSuper->connections[tensorSuperIndexOnQubit] = lastTensorId;
375 lastTensorSuper->connectionsIndices[tensorSuperIndexOnQubit] =
376 tensorIndexOnQubit;
377 }
378 }
379
380 void Disconnect() {
381 for (Types::qubit_t q = 0; q < GetNumQubits(); ++q) {
382 const auto lastTensorId = lastTensors[q];
383 const auto lastTensorSuperId = lastTensorsSuper[q];
384 const auto &lastTensor = tensors[lastTensorId];
385 const auto &lastTensorSuper = tensors[lastTensorSuperId];
386
387 const auto tensorIndexOnQubit = lastTensorIndices[q];
388 const auto tensorSuperIndexOnQubit = lastTensorIndicesSuper[q];
389
390 // tensor connection to the super one
391 lastTensor->connections[tensorIndexOnQubit] = TensorNode::NotConnected;
392 lastTensor->connectionsIndices[tensorIndexOnQubit] =
394
395 // the super tensor connected to the left tensor
396 lastTensorSuper->connections[tensorSuperIndexOnQubit] =
398 lastTensorSuper->connectionsIndices[tensorSuperIndexOnQubit] =
400 }
401 }
402
411 void SetMultithreading(bool multithreading = true) {
412 enableMultithreading = multithreading;
413 }
414
422 bool GetMultithreading() const { return enableMultithreading; }
423
424 std::unique_ptr<TensorNetwork> Clone() const {
425 auto cloned = std::make_unique<TensorNetwork>(0);
426
427 cloned->tensors = tensors; // all tensors in the network
428
429 cloned->lastTensors =
430 lastTensors; // the indices of the last tensors in the network
431 cloned->lastTensorsSuper =
432 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
457 // network
458 cloned->saveLastTensorIndicesSuper =
459 saveLastTensorIndicesSuper; // the indices of the last super tensors in
460 // the network
461
462 cloned->saveQubitsMap =
463 saveQubitsMap;
465 cloned->saveQubitsGroups =
466 saveQubitsGroups;
468
469 if (contractor) cloned->contractor = contractor->Clone();
470
471 return cloned;
472 }
473
474 private:
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 =
557 std::make_shared<Utils::Tensor<>>(lastTensor->tensor->Contract(
558 *(tensorNode->tensor), lastTensorIndexForQubit, 0,
559 enableMultithreading));
560 // TODO: Instead of Shuffle try by swapping qubits and indices... but also
561 // take care of connections if on the other qubit there is already a gate
562 // added
563 static const std::vector<size_t> indices{0, 1, 3, 2};
564 if (lastTensorIndexForQubit == 2) {
565 // this works, but it's slow, so let's try something else
566 // lastTensor->tensor =
567 // std::make_shared<Utils::Tensor<>>(std::move(lastTensor->tensor->Shuffle(indices)));
568 const auto otherQubit = lastTensor->qubits[3];
569 const auto otherTensorId = lastTensors[otherQubit];
570
571 std::swap(lastTensor->qubits[2], lastTensor->qubits[3]);
572 std::swap(lastTensor->connections[2], lastTensor->connections[3]);
573 std::swap(lastTensor->connectionsIndices[2],
574 lastTensor->connectionsIndices[3]);
575
576 lastTensorIndices[q] = 3;
577
578 if (otherTensorId == lastTensorId)
579 lastTensorIndices[otherQubit] = 2;
580 else {
581 // another tensor went on top of the other qubit/leg, so update
582 // connections
583 const auto &otherTensor =
584 tensors[lastTensor->connections[2]]; // 3 was swapped with 2
585
586 const auto otherIndex =
587 lastTensor->connectionsIndices[2]; // 3 was swapped with 2
588 otherTensor->connectionsIndices[otherIndex] =
589 2; // 3 was swapped with 2
590 }
591 }
592
593 tensorNode->SetSuper();
594
595 lastTensorId = lastTensorsSuper[q];
596 const auto &lastTensorSuper = tensors[lastTensorId];
597 lastTensorIndexForQubit = lastTensorIndicesSuper[q];
598
599 lastTensorSuper->tensor =
600 std::make_shared<Utils::Tensor<>>(lastTensorSuper->tensor->Contract(
601 *(tensorNode->tensor), lastTensorIndexForQubit, 0,
602 enableMultithreading));
603
604 if (lastTensorIndexForQubit == 2) {
605 // lastTensorSuper->tensor =
606 // std::make_shared<Utils::Tensor<>>(std::move(lastTensorSuper->tensor->Shuffle(indices)));
607 const auto otherQubit = lastTensorSuper->qubits[3];
608 const auto otherTensorId = lastTensorsSuper[otherQubit];
609
610 std::swap(lastTensorSuper->qubits[2], lastTensorSuper->qubits[3]);
611 std::swap(lastTensorSuper->connections[2],
612 lastTensorSuper->connections[3]);
613 std::swap(lastTensorSuper->connectionsIndices[2],
614 lastTensorSuper->connectionsIndices[3]);
615
616 lastTensorIndicesSuper[q] = 3;
617
618 if (otherTensorId == lastTensorId)
619 lastTensorIndicesSuper[otherQubit] = 2;
620 else {
621 // another tensor went on top of the other qubit/leg, so update
622 // connections
623 const auto &otherTensor =
624 tensors[lastTensorSuper->connections[2]]; // 3 was swapped with 2
625
626 const auto otherIndex =
627 lastTensorSuper->connectionsIndices[2]; // 3 was swapped with 2
628 otherTensor->connectionsIndices[otherIndex] =
629 2; // 3 was swapped with 2
630 }
631 }
632 } else {
633 auto tensorId = static_cast<Index>(tensors.size());
634 tensorNode->SetId(tensorId);
635
636 // connect the tensor to the last tensors on the qubits
637
638 // first, tensors from the network are connected to the new tensor
639 // that is, their output indices are connected to the input indices of the
640 // new tensor second, the new tensor is connected to the tensors from the
641 // network
642
643 lastTensor->connections[lastTensorIndexForQubit] = tensorId;
644 lastTensor->connectionsIndices[lastTensorIndexForQubit] =
645 0; // connect it to the zero index of the new tensor
646
647 tensorNode->connections[0] = lastTensorId;
648 tensorNode->connectionsIndices[0] = lastTensorIndexForQubit;
649
650 tensors.emplace_back(std::move(tensorNode));
651
652 // now set the last tensors and indices
653 lastTensors[q] = tensorId;
654
655 lastTensorIndices[q] = 1;
656
657 // add the 'super' now
658
659 tensorNode = std::make_shared<TensorNode>();
660
661 tensorNode->SetGate(gate, q);
662 tensorNode->SetSuper();
663
664 tensorId = static_cast<Index>(tensors.size());
665 tensorNode->SetId(tensorId);
666
667 // connect the tensor to the last tensors on the qubits
668
669 // first, tensors from the network are connected to the new tensor
670 // that is, their output indices are connected to the input indices of the
671 // new tensor second, the new tensor is connected to the tensors from the
672 // network
673
674 lastTensorId = lastTensorsSuper[q];
675 const auto &lastTensorSuper = tensors[lastTensorId];
676 lastTensorIndexForQubit = lastTensorIndicesSuper[q];
677 lastTensorSuper->connections[lastTensorIndexForQubit] = tensorId;
678 lastTensorSuper->connectionsIndices[lastTensorIndexForQubit] = 0;
679
680 tensorNode->connections[0] = lastTensorId;
681 tensorNode->connectionsIndices[0] = lastTensorIndexForQubit;
682
683 tensors.emplace_back(std::move(tensorNode));
684
685 // now set the last tensors and indices
686 lastTensorsSuper[q] = tensorId;
687
688 lastTensorIndicesSuper[q] = 1;
689 }
690 }
691
692 void AddTwoQubitsGate(
693 const QC::Gates::QuantumGateWithOp<TensorNode::MatrixClass> &gate,
695 const size_t q1group = qubitsMap[q1];
696 const size_t q2group = qubitsMap[q2];
697
698 if (q1group != q2group) {
699 // merge groups - move all qubits from the second group to the first one
700 for (const auto qg : qubitsGroups[q2group]) {
701 qubitsMap[qg] = q1group;
702 qubitsGroups[q1group].insert(qg);
703 }
704 qubitsGroups.erase(q2group); // remove the second group
705 }
706
707 // TODO: if first two qubits gate on the 1-qubit tensors of the qubits,
708 // contract them with this node????? or even if only one of them is on a
709 // 1-qubit tensor, contract it with this one????
710
711 auto tensorNode = std::make_shared<TensorNode>();
712 tensorNode->SetGate(gate, q1, q2);
713
714 auto tensorId = static_cast<Index>(tensors.size());
715 tensorNode->SetId(tensorId);
716
717 // connect the tensor to the last tensors on the qubits
718
719 // first, tensors from the network are connected to the new tensor
720 // that is, their output indices are connected to the input indices of the
721 // new tensor second, the new tensor is connected to the tensors from the
722 // network
723
724 auto lastTensorId = lastTensors[q1];
725
726 const auto &lastTensor = tensors[lastTensorId];
727 auto lastTensorIndexForQubit = lastTensorIndices[q1];
728 lastTensor->connections[lastTensorIndexForQubit] = tensorId;
729 lastTensor->connectionsIndices[lastTensorIndexForQubit] =
730 0; // connect it to the zero index of the new tensor
731
732 tensorNode->connections[0] = lastTensorId;
733 tensorNode->connectionsIndices[0] = lastTensorIndexForQubit;
734
735 lastTensorId = lastTensors[q2];
736 const auto &lastTensor2 = tensors[lastTensorId];
737 lastTensorIndexForQubit = lastTensorIndices[q2];
738 lastTensor2->connections[lastTensorIndexForQubit] = tensorId;
739 lastTensor2->connectionsIndices[lastTensorIndexForQubit] =
740 1; // connect it to the first index of the new tensor
741
742 tensorNode->connections[1] = lastTensorId;
743 tensorNode->connectionsIndices[1] = lastTensorIndices[q2];
744
745 tensors.emplace_back(std::move(tensorNode));
746
747 // now set the last tensors and indices
748 lastTensors[q1] = tensorId;
749 lastTensors[q2] = tensorId;
750
751 lastTensorIndices[q1] = 2;
752 lastTensorIndices[q2] = 3;
753
754 // add the 'super' now
755
756 tensorNode = std::make_shared<TensorNode>();
757
758 tensorNode->SetGate(gate, q1, q2);
759 tensorNode->SetSuper();
760
761 tensorId = static_cast<Index>(tensors.size());
762 tensorNode->SetId(tensorId);
763
764 // connect the tensor to the last tensors on the qubits
765
766 // first, tensors from the network are connected to the new tensor
767 // that is, their output indices are connected to the input indices of the
768 // new tensor second, the new tensor is connected to the tensors from the
769 // network
770
771 lastTensorId = lastTensorsSuper[q1];
772 const auto &lastTensorSuper = tensors[lastTensorId];
773 lastTensorIndexForQubit = lastTensorIndicesSuper[q1];
774 lastTensorSuper->connections[lastTensorIndexForQubit] = tensorId;
775 lastTensorSuper->connectionsIndices[lastTensorIndexForQubit] = 0;
776
777 tensorNode->connections[0] = lastTensorId;
778 tensorNode->connectionsIndices[0] = lastTensorIndexForQubit;
779
780 lastTensorId = lastTensorsSuper[q2];
781 const auto &lastTensor2Super = tensors[lastTensorId];
782 lastTensorIndexForQubit = lastTensorIndicesSuper[q2];
783 lastTensor2Super->connections[lastTensorIndexForQubit] = tensorId;
784 lastTensor2Super->connectionsIndices[lastTensorIndexForQubit] = 1;
785
786 tensorNode->connections[1] = lastTensorId;
787 tensorNode->connectionsIndices[1] = lastTensorIndicesSuper[q2];
788
789 tensors.emplace_back(std::move(tensorNode));
790
791 // now set the last tensors and indices
792 lastTensorsSuper[q1] = tensorId;
793 lastTensorsSuper[q2] = tensorId;
794
795 lastTensorIndicesSuper[q1] = 2;
796 lastTensorIndicesSuper[q2] = 3;
797 }
798
799 double Contract() {
800 if (!contractor) return 0;
801 contractor->SetMultithreading(enableMultithreading);
802
803 Connect();
804
805 // contract the network, save result to return
806 double result = 1;
807
808 for (auto &group : qubitsGroups) {
809 const double groupResult = contractor->Contract(
810 *this,
811 *group.second.begin()); // doesn't really matter which qubit is
812 // passed as long it belongs to the group
813 if (groupResult == 0) return 0;
814 result *= groupResult;
815 }
816
817 // no need to actually disconnect the indices, as they are not used unless
818 // they are connected again when adding a new gate/projection for
819 // measurement, they are reconnected anyway, the same goes for doing another
820 // contraction
821
822 // return the result
823 return result;
824 }
825
826 double Contract(const std::unordered_set<Types::qubit_t> &qubits) {
827 if (!contractor) return 0;
828 contractor->SetMultithreading(enableMultithreading);
829
830 Connect();
831
832 // contract the network, save result to return
833 double result = 1;
834
835 for (auto &group : qubitsGroups) {
836 // this is probably less costly than contracting
837 for (const auto q : group.second)
838 if (qubits.find(q) != qubits.end()) {
839 // this group has at least one qubit in the set, contract it
840 // break to avoid contracting it multiple times
841 const double groupResult = contractor->Contract(
842 *this, q); // doesn't really matter which qubit is passed as long
843 // it belongs to the group
844 if (groupResult == 0) return 0;
845 result *= groupResult;
846 break;
847 }
848 }
849
850 // no need to actually disconnect the indices, as they are not used unless
851 // they are connected again when adding a new gate/projection for
852 // measurement, they are reconnected anyway, the same goes for doing another
853 // contraction
854
855 // return the result
856 return result;
857 }
858
859 void Clean(size_t numQubits) { SetQubitsTensors(numQubits); }
860
861 void SetQubitsTensors(size_t numQubits) {
862 for (Types::qubit_t q = 0; q < numQubits; ++q) {
863 qubitsMap[q] = q; // the qubit group id is the qubit number itself
864 qubitsGroups[q].insert(
865 q); // the qubit group contains only the qubit itself
866
867 auto tensorNode = std::make_shared<TensorNode>();
868 tensorNode->SetQubit(q);
869 tensorNode->SetId(static_cast<Index>(tensors.size()));
870
871 lastTensors[q] = tensorNode->GetId();
872 lastTensorIndices[q] = 0;
873 tensors.emplace_back(std::move(tensorNode));
874
875 tensorNode = std::make_shared<TensorNode>();
876 tensorNode->SetQubit(q);
877 tensorNode->SetId(static_cast<Index>(tensors.size()));
878 tensorNode->SetSuper();
879
880 lastTensorsSuper[q] = tensorNode->GetId();
881 lastTensorIndicesSuper[q] = 0;
882 tensors.emplace_back(std::move(tensorNode));
883 }
884 }
885
886 void SetQubitsTensorsClear(size_t numQubits) {
887 for (Types::qubit_t q = 0; q < numQubits; ++q) {
888 qubitsMap[q] = q; // the qubit group id is the qubit number itself
889 qubitsGroups[q].insert(
890 q); // the qubit group contains only the qubit itself
891
892 lastTensors[q] = 2 * q;
893 lastTensorIndices[q] = 0;
894
895 lastTensorsSuper[q] = lastTensors[q] + 1;
896 lastTensorIndicesSuper[q] = 0;
897 }
898 }
899
900 std::vector<std::shared_ptr<TensorNode>>
901 tensors; // all tensors in the network
902
903 std::vector<Index>
904 lastTensors; // the indices of the last tensors in the network
905 std::vector<Index>
906 lastTensorsSuper; // the indices of the last super tensors in the network
907
908 std::vector<Index>
909 lastTensorIndices; // the indices of the last tensors in the network
910 std::vector<Index> lastTensorIndicesSuper; // the indices of the last super
911 // tensors in the network
912
913 std::vector<size_t> qubitsMap;
915 std::unordered_map<size_t, std::unordered_set<Types::qubit_t>>
916 qubitsGroups;
918
919 size_t savedTensorsNr = 0;
920 std::vector<std::shared_ptr<TensorNode>>
921 saveTensors; // all tensors in the network
922
923 std::vector<Index>
924 saveLastTensors; // the indices of the last tensors in the network
925 std::vector<Index> saveLastTensorsSuper; // the indices of the last super
926 // tensors in the network
927
928 std::vector<Index>
929 saveLastTensorIndices; // the indices of the last tensors in the network
930 std::vector<Index>
931 saveLastTensorIndicesSuper; // the indices of the last
932 // super tensors in the network
933
934 std::vector<size_t>
935 saveQubitsMap;
937 std::unordered_map<size_t, std::unordered_set<Types::qubit_t>>
938 saveQubitsGroups;
940
941 std::shared_ptr<ITensorContractor> contractor;
942
943 bool enableMultithreading = true;
944
945 std::mt19937_64 rng;
946 std::uniform_real_distribution<double> uniformZeroOne;
947};
948
949} // namespace TensorNetworks
950
951#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.
void SetSeed(uint64_t seed)
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:151
uint_fast64_t qubit_t
The type of a qubit.
Definition Types.h:21