45 for (
size_t i = 0; i < original.size(); ++i)
46 if (original[i] && !original[i]->qubits.empty() && group.count(original[i]->qubits[0]))
47 plan.inputs.push_back(i);
48 if (plan.inputs.empty())
49 throw std::invalid_argument(
"Cannot contract an empty tensor network");
51 bool cacheable = MakeSignature(network, qubit, plan);
54 for (
auto it = plans.begin(); it != plans.end(); ++it)
56 if (it->fingerprint == plan.fingerprint && it->signature == plan.signature)
58 plans.splice(plans.begin(), plans, it);
60 if (!plans.front().preparationAttempted)
61 PreparePlan(network, plans.front());
62 return ExecutePlan(network, plans.front());
68 std::unordered_map<Eigen::Index, size_t> slots;
69 std::unordered_map<Eigen::Index, size_t> versions;
70 tensors.reserve(plan.inputs.size());
72 slots.reserve(plan.inputs.size());
73 versions.reserve(plan.inputs.size());
75 for (
size_t slot = 0; slot < plan.inputs.size(); ++slot)
77 const auto &node = original[plan.inputs[slot]];
78 tensors.emplace(node->GetId(), node->CloneWithoutTensorCopy());
80 slots.emplace(node->GetId(), slot);
81 versions.emplace(node->GetId(), 0);
84 std::priority_queue<Candidate, std::vector<Candidate>, CandidateLater> candidates;
85 for (
const auto &entry : tensors)
86 AddCandidates(entry.first, tensors, versions, candidates,
true);
88 Eigen::Index resultId = tensors.begin()->first;
89 while (tensors.size() > 1)
91 while (!candidates.empty())
93 const auto &candidate = candidates.top();
94 const auto a = versions.find(candidate.a), b = versions.find(candidate.b);
95 if (a != versions.end() && b != versions.end() && a->second == candidate.versionA && b->second == candidate.versionB)
99 if (candidates.empty())
100 throw std::invalid_argument(
"Tensor network has no connected contraction pair");
101 const Candidate candidate = candidates.top();
107 step.left = slots.at(candidate.a);
108 step.right = slots.at(candidate.b);
109 const auto &node = tensors.at(candidate.a);
110 for (
size_t i = 0; i < node->connections.size(); ++i)
111 if (node->connections[i] == candidate.b)
112 step.axes.emplace_back(i, node->connectionsIndices[i]);
113 plan.axisBytes += step.axes.capacity() *
sizeof(step.axes[0]);
114 plan.steps.push_back(std::move(step));
115 if (PlanBytes(plan) > planCacheByteLimit)
118 plan.signature.clear();
123 resultId =
ContractNodes(qubit, tensors, candidate.a, candidate.b, candidate.resultRank);
124 versions.erase(candidate.b);
125 ++versions.at(candidate.a);
126 if (candidate.resultRank == 0)
128 if (tensors.size() == 1 || tensors.at(resultId)->contractsTheNeededQubit)
130 tensors.erase(resultId);
131 versions.erase(resultId);
135 AddCandidates(resultId, tensors, versions, candidates,
false);
139 const double result = std::real(tensors.at(resultId)->tensor->atOffset(0));
142 plan.resultSlot = slots.at(resultId);
144 plan.bytes = PlanBytes(plan);
145 if (plan.bytes <= planCacheByteLimit)
147 while (!plans.empty() && cachedBytes > planCacheByteLimit - plan.bytes)
148 if (!DropLastPreparedPlan())
150 cachedBytes += plan.bytes;
151 plans.push_front(std::move(plan));
161 planCacheByteLimit = bytes;
162 while (!plans.empty() && cachedBytes > bytes)
163 if (!DropLastPreparedPlan())
165 for (
auto &plan : plans)
167 plan.preparationAttempted =
false;
174 return planCacheByteLimit;
189 return planCacheHits;
194 return std::count_if(plans.begin(), plans.end(), [](
const auto &plan) { return bool(plan.prepared); });
201 workspaceByteLimit = bytes;
208 return workspaceByteLimit;
213 return workspace.capacity() *
sizeof(Complex);
224 std::shared_ptr<ITensorContractor>
Clone()
const override
226 auto cloned = std::make_shared<ForestContractor>();
229 cloned->planCacheByteLimit = planCacheByteLimit;
230 cloned->workspaceByteLimit = workspaceByteLimit;
237 using Complex = std::complex<double>;
242 size_t outputOffset = 0;
247 std::vector<PreparedStep> steps;
248 size_t bytes = 0, workspaceSize = 0, packingSize = 0;
253 size_t left = 0, right = 0;
254 std::vector<std::pair<size_t, size_t>> axes;
259 std::vector<size_t> signature, inputs;
260 std::vector<Step> steps;
261 size_t fingerprint = 0, resultSlot = 0, maxRank = 0;
262 size_t axisBytes = 0, bytes = 0;
264 std::shared_ptr<const PreparedPlan> prepared;
265 bool preparationAttempted =
false;
271 size_t versionA, versionB, resultRank, edge;
272 double outputSize, work;
275 struct CandidateLater
277 bool operator()(
const Candidate &a,
const Candidate &b)
const
279 if (a.outputSize != b.outputSize)
280 return a.outputSize > b.outputSize;
281 if (a.work != b.work)
282 return a.work > b.work;
287 if (a.edge != b.edge)
288 return a.edge > b.edge;
293 using Candidates = std::priority_queue<Candidate, std::vector<Candidate>, CandidateLater>;
295 static void AddCandidates(Eigen::Index
id,
const TensorsMap &tensors,
const std::unordered_map<Eigen::Index, size_t> &versions, Candidates &candidates,
298 const auto &node = tensors.at(
id);
299 for (
size_t i = 0; i < node->connections.size(); ++i)
301 const auto next = node->connections[i];
305 if (std::find(node->connections.begin(), node->connections.begin() + i, next) != node->connections.begin() + i)
307 const auto aId = std::max(
id, next), bId = std::min(
id, next);
308 const auto &a = tensors.at(aId), &b = tensors.at(bId);
309 Candidate candidate{aId, bId, versions.at(aId), versions.at(bId), 0, 0, 0, 0};
310 double reductionSize = 0;
312 for (
size_t axis = 0; axis < a->connections.size(); ++axis)
314 const double dimension = std::log2(
double(a->tensor->GetDim(axis)));
315 if (a->connections[axis] == bId)
317 reductionSize += dimension;
320 candidate.edge = axis;
326 ++candidate.resultRank;
327 candidate.outputSize += dimension;
330 for (
size_t axis = 0; axis < b->connections.size(); ++axis)
331 if (b->connections[axis] != aId)
333 ++candidate.resultRank;
334 candidate.outputSize += std::log2(
double(b->tensor->GetDim(axis)));
337 candidate.work = candidate.outputSize + reductionSize;
338 candidates.push(candidate);
342 bool MakeSignature(
const TensorNetwork &network,
Types::qubit_t qubit, CachedPlan &plan)
const
344 if (!planCacheByteLimit)
346 auto append = [&](
size_t value) {
347 plan.signature.push_back(value);
348 plan.fingerprint ^= value + size_t(0x9e3779b97f4a7c15ULL) + (plan.fingerprint << 6) + (plan.fingerprint >> 2);
350 append(
static_cast<size_t>(qubit));
351 for (
auto index : plan.inputs)
353 const auto &node = network.GetTensors()[index];
355 append(
static_cast<size_t>(node->GetId()));
356 append(node->GetRank());
357 append(node->tensor->IsDummy());
358 for (
auto dimension : node->tensor->GetDims())
360 append(node->connections.size());
361 for (
auto connection : node->connections)
362 append(
static_cast<size_t>(connection));
363 append(node->connectionsIndices.size());
364 for (
auto axis : node->connectionsIndices)
365 append(
static_cast<size_t>(axis));
366 append(node->qubits.size());
367 for (
auto q : node->qubits)
368 append(
static_cast<size_t>(q));
369 if (PlanBytes(plan) > planCacheByteLimit)
375 double ExecutePlan(
const TensorNetwork &network,
const CachedPlan &plan)
378 return ExecutePrepared(network, plan);
379 std::vector<std::shared_ptr<const Utils::Tensor<>>> values;
380 values.reserve(plan.inputs.size());
381 for (
auto index : plan.inputs)
382 values.push_back(network.GetTensors()[index]->tensor);
383 for (
const auto &step : plan.steps)
385 values[step.left] = std::make_shared<Utils::Tensor<>>(values[step.left]->Contract(*values[step.right], step.axes,
enableMultithreading));
386 values[step.right].reset();
389 return std::real((*values[plan.resultSlot])[
size_t(0)]);
394 void PreparePlan(
const TensorNetwork &network, CachedPlan &plan)
396 plan.preparationAttempted =
true;
399 size_t limit = planCacheByteLimit - cachedBytes;
400 for (
const auto &cached : plans)
402 limit += cached.prepared->bytes;
403 size_t bytes =
sizeof(PreparedPlan) + 2 *
sizeof(
void *);
406 auto prepared = std::make_shared<PreparedPlan>();
407 prepared->steps.reserve(plan.steps.size());
408 bytes =
sizeof(PreparedPlan) + 2 *
sizeof(
void *);
412 std::vector<const std::vector<size_t> *> dimensions;
413 dimensions.reserve(plan.inputs.size());
414 for (
auto index : plan.inputs)
416 const auto &tensor = network.GetTensors()[index]->tensor;
417 if (tensor->IsDummy())
419 dimensions.push_back(&tensor->GetDims());
421 const size_t none = std::numeric_limits<size_t>::max();
422 std::vector<size_t> active(plan.inputs.size(), none), free, capacities;
423 for (
const auto &step : plan.steps)
425 prepared->steps.emplace_back();
426 auto &item = prepared->steps.back();
427 if (!item.contraction.Prepare(*dimensions[step.left], *dimensions[step.right], step.axes, limit - bytes))
429 bytes += item.contraction.ExtraBytes();
430 prepared->packingSize = std::max(prepared->packingSize, item.contraction.GetPackingSize());
431 dimensions[step.left] = &item.contraction.GetDims();
432 dimensions[step.right] =
nullptr;
433 const size_t size = item.contraction.GetSize();
437 buffer = capacities.size();
438 capacities.push_back(size);
442 const auto best = std::min_element(free.begin(), free.end(), [&](
size_t a,
size_t b) {
443 const bool fitA = capacities[a] >= size, fitB = capacities[b] >= size;
446 return fitA ? capacities[a] < capacities[b] : capacities[a] > capacities[b];
450 capacities[buffer] = std::max(capacities[buffer], size);
452 item.outputOffset = buffer;
454 if (active[step.left] != none)
455 free.push_back(active[step.left]);
456 if (active[step.right] != none)
457 free.push_back(active[step.right]);
458 active[step.left] = buffer;
459 active[step.right] = none;
462 size_t arenaSize = 0;
464 for (
auto &capacity : capacities)
466 const size_t size = capacity;
467 capacity = arenaSize;
476 prepared->workspaceSize = arenaSize;
477 for (
auto &step : prepared->steps)
478 step.outputOffset = capacities[step.outputOffset];
480 prepared->bytes = bytes;
481 if (bytes > planCacheByteLimit)
483 while (cachedBytes > planCacheByteLimit - bytes)
484 if (!DropLastPreparedPlan())
486 plan.prepared = std::move(prepared);
488 cachedBytes += bytes;
491 bool PrepareWorkspace(
size_t size)
493 if (!size || size > workspaceByteLimit /
sizeof(Complex))
495 if (workspace.size() < size)
498 std::vector<Complex> replacement(size);
499 if (replacement.capacity() > workspaceByteLimit /
sizeof(Complex))
501 workspace.swap(replacement);
506 void ClearWorkspace()
508 std::vector<Complex>().swap(workspace);
511 double ExecutePrepared(
const TensorNetwork &network,
const CachedPlan &plan)
513 const auto &prepared = *plan.prepared;
516 size_t size = prepared.workspaceSize;
517 const bool reusePacking = size && prepared.packingSize &&
519 const bool reuse = reusePacking || PrepareWorkspace(prepared.workspaceSize);
520 Complex *packing = reusePacking ? workspace.data() + prepared.workspaceSize :
nullptr;
521 std::vector<const Complex *> values;
522 values.reserve(plan.inputs.size());
523 for (
auto index : plan.inputs)
524 values.push_back(&(*network.GetTensors()[index]->tensor)[
size_t(0)]);
525 std::vector<std::unique_ptr<Complex[]>> owned(reuse ? 0 : plan.inputs.size());
526 for (
size_t i = 0; i < plan.steps.size(); ++i)
528 const auto &step = plan.steps[i];
529 const auto &item = prepared.steps[i];
530 std::unique_ptr<Complex[]> fresh;
533 dest = workspace.data() + item.outputOffset;
536 fresh.reset(
new Complex[item.contraction.GetSize()]);
539 item.contraction.Execute(values[step.left], values[step.right], dest,
enableMultithreading, packing);
542 owned[step.left] = std::move(fresh);
543 owned[step.right].reset();
545 values[step.left] = dest;
546 values[step.right] =
nullptr;
549 return values[plan.resultSlot][0].real();
552 static size_t PlanBytes(
const CachedPlan &plan)
554 return sizeof(CachedPlan) + 2 *
sizeof(
void *) + plan.axisBytes + (plan.signature.capacity() + plan.inputs.capacity()) *
sizeof(size_t) +
555 plan.steps.capacity() *
sizeof(Step) + (plan.prepared ? plan.prepared->bytes : 0);
560 cachedBytes -= plans.back().bytes;
564 bool DropLastPreparedPlan()
566 for (
auto it = plans.rbegin(); it != plans.rend(); ++it)
569 cachedBytes -= it->prepared->bytes;
570 it->bytes -= it->prepared->bytes;
571 it->prepared.reset();
579 size_t planCacheByteLimit = 8 * 1024 * 1024;
580 size_t cachedBytes = 0, planCacheHits = 0;
581 size_t workspaceByteLimit = 16 * 1024 * 1024;
582 std::vector<Complex> workspace;
583 std::list<CachedPlan> plans;