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