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