Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
block_tensor_svd.hpp
Go to the documentation of this file.
1
7#pragma once
8
11#include <uni20/core/math.hpp>
19#include <uni20/tensor/copy.hpp>
23
24#include <algorithm>
25#include <array>
26#include <cstddef>
27#include <cstdint>
28#include <functional>
29#include <map>
30#include <memory>
31#include <numeric>
32#include <optional>
33#include <ranges>
34#include <span>
35#include <stdexcept>
36#include <string>
37#include <type_traits>
38#include <utility>
39#include <vector>
40
41namespace uni20
42{
43
44namespace detail
45{
46template <class Context> class BlockSvdAllocationContextBinding {
47 public:
48 explicit BlockSvdAllocationContextBinding(Context& context) noexcept : context_(std::addressof(context)) {}
49
50 [[nodiscard]] auto get() const noexcept -> Context& { return *context_; }
51
52 private:
53 Context* context_;
54};
55
56template <> class BlockSvdAllocationContextBinding<void> {
57 public:
58 BlockSvdAllocationContextBinding() noexcept = default;
59};
60} // namespace detail
61
66{
68 std::size_t index;
69
71 auto operator==(BlockSvdStateId const&) const -> bool = default;
72};
73
75template <uni20::Real Real> struct BlockSvdState
76{
79};
80
81template <uni20::Real Real> class BlockSvdSelection;
82
83template <uni20::Real Real>
84[[nodiscard]] auto make_svd_selection(std::span<BlockSvdState<Real> const> spectrum,
85 std::span<BlockSvdStateId const> requested) -> BlockSvdSelection<Real>;
86
87template <uni20::Real Real>
88[[nodiscard]] auto select_svd_states(std::span<BlockSvdState<Real> const> spectrum,
89 linalg::SvdTruncationPolicy<Real> const& policy = {}) -> BlockSvdSelection<Real>;
90
92template <uni20::Real Real> class BlockSvdSelection {
93 public:
95 auto state_ids() const noexcept -> std::span<BlockSvdStateId const> { return state_ids_; }
96
98 auto truncation() const noexcept -> linalg::SvdTruncationInfo<Real> const& { return truncation_; }
99
100 private:
101 BlockSvdSelection(std::vector<BlockSvdStateId> state_ids, linalg::SvdTruncationInfo<Real> truncation)
102 : state_ids_(std::move(state_ids)), truncation_(std::move(truncation))
103 {}
104
105 template <uni20::Real OtherReal>
106 friend auto make_svd_selection(std::span<BlockSvdState<OtherReal> const> spectrum,
107 std::span<BlockSvdStateId const> requested) -> BlockSvdSelection<OtherReal>;
108
109 template <uni20::Real OtherReal>
110 friend auto select_svd_states(std::span<BlockSvdState<OtherReal> const> spectrum,
112
113 std::vector<BlockSvdStateId> state_ids_;
115};
116
119 public:
121 explicit BlockSvdVectorSelection(std::vector<BlockSvdStateId> state_ids) : state_ids_(std::move(state_ids)) {}
122
124 auto state_ids() const noexcept -> std::span<BlockSvdStateId const> { return state_ids_; }
125
126 private:
127 std::vector<BlockSvdStateId> state_ids_;
128};
129
132{
133 std::string bond_label = {};
134};
135
137template <class LeftTensor, class SingularValues, class RightAdjointTensor, class TruncationInfo>
139{
141 SingularValues singular_values;
143 TruncationInfo truncation;
144};
145
146namespace detail
147{
148
149template <class Boundary> struct SvdBoundaryShape;
150
151template <template <class...> class Boundary, class... Spaces>
152struct SvdBoundaryShape<Boundary<Spaces...>> : BoundaryBlockShape<Boundary<Spaces...>>
153{};
154
155template <std::size_t KeyCoordinateCount, std::size_t DenseOrder> struct SvdBoundaryFragment
156{
157 QNum charge;
158 std::array<std::size_t, KeyCoordinateCount> coordinates{};
159 std::array<std::size_t, DenseOrder> extents{};
160 std::size_t flattened_extent = 1;
161 std::size_t sector_offset = 0;
162};
163
164template <class Fragment> struct SvdBoundarySector
165{
166 QNum charge = {};
167 std::vector<Fragment> fragments = {};
168 std::size_t flattened_extent = 0;
169};
170
171template <std::size_t I = 0, class Boundary, class Fragment>
172void enumerate_svd_boundary_fragments(Boundary const& boundary, QNum charge, Fragment fragment, std::size_t key_axis,
173 std::size_t dense_axis, std::vector<Fragment>& fragments)
174{
175 if constexpr (I == Boundary::size())
176 {
177 fragment.charge = charge;
178 fragment.flattened_extent = 1;
179 for (std::size_t extent : fragment.extents)
180 fragment.flattened_extent *= extent;
181 fragments.push_back(std::move(fragment));
182 }
183 else
184 {
185 auto const& space = boundary.template space<I>();
186 using space_type = std::remove_cvref_t<decltype(space)>;
187 constexpr bool has_coordinate = BlockTensorSpaceTraits<space_type>::has_block_coordinate;
188 constexpr bool has_dense_axis = BlockTensorSpaceTraits<space_type>::has_dense_axis;
189 std::size_t const coordinate_count = has_coordinate ? factor_size(space) : 1;
190
191 for (std::size_t coordinate = 0; coordinate < coordinate_count; ++coordinate)
192 {
193 Fragment next = fragment;
194 if constexpr (has_coordinate) next.coordinates[key_axis] = coordinate;
195 if constexpr (has_dense_axis) next.extents[dense_axis] = factor_extent(space, coordinate);
196 enumerate_svd_boundary_fragments<I + 1>(boundary, charge + factor_qnum(space, coordinate), std::move(next),
197 key_axis + (has_coordinate ? 1 : 0),
198 dense_axis + (has_dense_axis ? 1 : 0), fragments);
199 }
200 }
201}
202
203template <class Boundary> auto make_svd_boundary_fragments(Symmetry symmetry, Boundary const& boundary)
204{
205 using shape = SvdBoundaryShape<Boundary>;
206 using fragment_type = SvdBoundaryFragment<shape::key_coordinate_count, shape::dense_block_order>;
207 std::vector<fragment_type> fragments;
208 enumerate_svd_boundary_fragments(boundary, QNum::identity(symmetry), fragment_type{}, 0, 0, fragments);
209 return fragments;
210}
211
212template <class Fragment>
213auto group_svd_boundary_fragments(std::vector<Fragment> fragments) -> std::vector<SvdBoundarySector<Fragment>>
214{
215 std::map<std::uint64_t, SvdBoundarySector<Fragment>> grouped;
216 for (auto& fragment : fragments)
217 {
218 auto const code = fragment.charge.raw_code();
219 auto [position, inserted] = grouped.try_emplace(code, SvdBoundarySector<Fragment>{.charge = fragment.charge});
220 static_cast<void>(inserted);
221 fragment.sector_offset = position->second.flattened_extent;
222 position->second.flattened_extent += fragment.flattened_extent;
223 position->second.fragments.push_back(std::move(fragment));
224 }
225
226 std::vector<SvdBoundarySector<Fragment>> result;
227 result.reserve(grouped.size());
228 for (auto& [code, sector] : grouped)
229 {
230 static_cast<void>(code);
231 result.push_back(std::move(sector));
232 }
233 return result;
234}
235
236template <std::size_t First, std::size_t Second>
237auto concatenate_coordinates(std::array<std::size_t, First> const& first,
238 std::array<std::size_t, Second> const& second) -> std::array<std::size_t, First + Second>
239{
240 std::array<std::size_t, First + Second> result{};
241 std::ranges::copy(first, result.begin());
242 std::ranges::copy(second, result.begin() + static_cast<std::ptrdiff_t>(First));
243 return result;
244}
245
246template <std::size_t Rank>
247auto column_major_indices(std::size_t linear,
248 std::array<std::size_t, Rank> const& extents) -> std::array<uni20::index_type, Rank>
249{
250 std::array<uni20::index_type, Rank> result{};
251 for (std::size_t axis = 0; axis < Rank; ++axis)
252 {
253 result[axis] = static_cast<uni20::index_type>(linear % extents[axis]);
254 linear /= extents[axis];
255 }
256 return result;
257}
258
259template <class Block, std::size_t Rank>
260decltype(auto) block_element(Block&& block, std::array<uni20::index_type, Rank> const& indices)
261{
262 return [&]<std::size_t... I>(std::index_sequence<I...>) -> decltype(auto) {
263 return std::forward<Block>(block)[indices[I]...];
264 }(std::make_index_sequence<Rank>{});
265}
266
267template <std::size_t First, std::size_t Second>
268auto concatenate_indices(std::array<uni20::index_type, First> const& first,
269 std::array<uni20::index_type, Second> const& second)
270 -> std::array<uni20::index_type, First + Second>
271{
272 std::array<uni20::index_type, First + Second> result{};
273 std::ranges::copy(first, result.begin());
274 std::ranges::copy(second, result.begin() + static_cast<std::ptrdiff_t>(First));
275 return result;
276}
277
278template <uni20::Real Real> class SvdNonnegativeSum {
279 public:
280 void add(Real value)
281 {
282 Real const next = sum_ + value;
283 if (sum_ >= value)
284 correction_ += (sum_ - next) + value;
285 else
286 correction_ += (value - next) + sum_;
287 sum_ = next;
288 }
289
290 auto value() const -> Real { return sum_ + correction_; }
291
292 private:
293 Real sum_{};
294 Real correction_{};
295};
296
297inline auto same_svd_state_id(BlockSvdStateId const& lhs, BlockSvdStateId const& rhs) -> bool { return lhs == rhs; }
298
299template <uni20::Real Real>
300auto canonical_svd_selection(std::span<BlockSvdState<Real> const> spectrum, std::span<BlockSvdStateId const> requested)
301 -> std::pair<std::vector<BlockSvdStateId>, std::vector<unsigned char>>
302{
303 std::vector<unsigned char> selected(spectrum.size(), 0);
304 for (BlockSvdStateId const& id : requested)
305 {
306 auto const found =
307 std::ranges::find_if(spectrum, [&](auto const& state) { return same_svd_state_id(state.id, id); });
308 if (found == spectrum.end()) throw std::invalid_argument("block-SVD selection contains an unknown state");
309 auto const ordinal = static_cast<std::size_t>(found - spectrum.begin());
310 if (selected[ordinal]) throw std::invalid_argument("block-SVD selection contains a repeated state");
311 selected[ordinal] = 1;
312 }
313
314 std::vector<BlockSvdStateId> canonical;
315 canonical.reserve(requested.size());
316 for (std::size_t ordinal = 0; ordinal < spectrum.size(); ++ordinal)
317 {
318 if (selected[ordinal]) canonical.push_back(spectrum[ordinal].id);
319 }
320 return {std::move(canonical), std::move(selected)};
321}
322
323template <uni20::Real Real>
324auto summarize_svd_selection(std::span<BlockSvdState<Real> const> spectrum,
325 std::span<unsigned char const> selected) -> linalg::SvdTruncationInfo<Real>
326{
327 Real scale{};
328 if (!spectrum.empty()) scale = spectrum.front().singular_value;
329
330 SvdNonnegativeSum<Real> total;
331 SvdNonnegativeSum<Real> discarded;
332 std::optional<Real> smallest_retained;
333 std::optional<Real> largest_discarded;
334 std::size_t retained_rank = 0;
335 for (std::size_t ordinal = 0; ordinal < spectrum.size(); ++ordinal)
336 {
337 Real const value = spectrum[ordinal].singular_value;
338 if (!uni20::isfinite(value) || value < Real{})
339 {
340 throw std::runtime_error("block-SVD spectrum contains an invalid singular value");
341 }
342 Real const square = scale == Real{} ? Real{} : (value / scale) * (value / scale);
343 total.add(square);
344 if (selected[ordinal])
345 {
346 ++retained_rank;
347 if (!smallest_retained || value < *smallest_retained) smallest_retained = value;
348 }
349 else if (!largest_discarded || value > *largest_discarded)
350 {
351 largest_discarded = value;
352 }
353 if (!selected[ordinal]) discarded.add(square);
354 }
355
356 Real const total_scaled = total.value();
357 return {.available_rank = spectrum.size(),
358 .retained_rank = retained_rank,
359 .original_squared_norm = scale * scale * total_scaled,
360 .discarded_weight = total_scaled == Real{} ? Real{} : discarded.value() / total_scaled,
361 .smallest_retained_singular_value = smallest_retained,
362 .largest_discarded_singular_value = largest_discarded};
363}
364
365template <class Selection>
366concept BlockSvdStateSelection = requires(Selection const& selection) {
367 { selection.state_ids() } -> std::same_as<std::span<BlockSvdStateId const>>;
368};
369
370template <class Function> void execute_svd_sector_batch(SerialBlockExecution, std::size_t size, Function&& function)
371{
372 for (std::size_t index = 0; index < size; ++index)
373 function(index);
374}
375
376template <class Function>
377void execute_svd_sector_batch(SchedulerBatchBlockExecution, std::size_t size, Function&& function)
378{
379 async::execute_batch(size, std::forward<Function>(function));
380}
381
382template <class LeftTensor, class SingularValueTensor, class RightAdjointTensor> struct BlockSvdFactorizationResult
383{
384 using left_tensor_type = LeftTensor;
385 using singular_value_tensor_type = SingularValueTensor;
386 using right_adjoint_tensor_type = RightAdjointTensor;
387 using real_type = tensor_element_t<singular_value_tensor_type>;
388
389 left_tensor_type left_singular_vectors;
390 singular_value_tensor_type singular_values;
391 std::vector<real_type> host_singular_values;
392 right_adjoint_tensor_type right_singular_vectors_adjoint;
393};
394
395template <class Exact> [[nodiscard]] auto retain_host_svd_result(Exact exact)
396{
397 using exact_type = std::remove_cvref_t<Exact>;
398 using left_type = typename exact_type::left_singular_vector_tensor_type;
399 using value_type = typename exact_type::singular_value_tensor_type;
400 using right_type = typename exact_type::right_singular_vector_adjoint_tensor_type;
401 using real_type = tensor_element_t<value_type>;
402 std::vector<real_type> host_values(static_cast<std::size_t>(exact.singular_values.extent(0)));
403 for (std::size_t index = 0; index < host_values.size(); ++index)
404 host_values[index] = exact.singular_values[static_cast<uni20::index_type>(index)];
405 return BlockSvdFactorizationResult<left_type, value_type, right_type>{
406 .left_singular_vectors = std::move(exact.left_singular_vectors),
407 .singular_values = std::move(exact.singular_values),
408 .host_singular_values = std::move(host_values),
409 .right_singular_vectors_adjoint = std::move(exact.right_singular_vectors_adjoint)};
410}
411
412} // namespace detail
413
422template <uni20::LapackScalar Scalar, class DomainType, class CodomainType, class BlockExecutionPolicy,
423 class MatrixTensor = ColumnMajorTensor<Scalar, 2>,
424 class SingularValueTensor = ColumnMajorTensor<uni20::make_real_t<Scalar>, 1>, class AllocationContext = void>
426 public:
429 using domain_type = DomainType;
430 using codomain_type = CodomainType;
431 using block_execution_policy = BlockExecutionPolicy;
432 using domain_shape = detail::SvdBoundaryShape<domain_type>;
433 using codomain_shape = detail::SvdBoundaryShape<codomain_type>;
435 detail::SvdBoundaryFragment<domain_shape::key_coordinate_count, domain_shape::dense_block_order>;
437 detail::SvdBoundaryFragment<codomain_shape::key_coordinate_count, codomain_shape::dense_block_order>;
439 using matrix_type = MatrixTensor;
440 using singular_value_tensor_type = SingularValueTensor;
441 using allocation_context_type = AllocationContext;
442
443 static_assert(domain_type::size() + 1 <= 4, "block-SVD right factor exceeds the first BlockTensor order limit");
444 static_assert(codomain_type::size() + 1 <= 4, "block-SVD left factor exceeds the first BlockTensor order limit");
445
447 struct Sector
448 {
450 std::vector<domain_fragment_type> domain_fragments;
451 std::vector<codomain_fragment_type> codomain_fragments;
452 std::vector<source_key_type> stored_source_keys;
455 std::vector<real_type> host_singular_values;
457 };
458
461 std::vector<Sector> sectors)
462 requires std::same_as<allocation_context_type, void>
463 : symmetry_(symmetry), domain_(std::move(domain)), codomain_(std::move(codomain)), options_(options),
464 sectors_(std::move(sectors))
465 {
466 this->build_spectrum();
467 }
468
470 template <class Context = allocation_context_type>
472 std::vector<Sector> sectors, Context& allocation_context)
473 requires(!std::same_as<Context, void>) && std::same_as<Context, allocation_context_type>
474 : symmetry_(symmetry), domain_(std::move(domain)), codomain_(std::move(codomain)), options_(options),
475 sectors_(std::move(sectors)), allocation_context_(allocation_context)
476 {
477 this->build_spectrum();
478 }
479
481 decltype(auto) allocation_context() const noexcept
482 requires(!std::same_as<allocation_context_type, void>)
483 {
484 return allocation_context_.get();
485 }
486
487 private:
488 void build_spectrum()
489 {
490 for (Sector const& sector : sectors_)
491 {
492 ERROR_IF(sector.host_singular_values.size() != static_cast<std::size_t>(sector.singular_values.extent(0)),
493 "block-SVD host spectrum does not match the retained provider values");
494 for (std::size_t index = 0; index < sector.host_singular_values.size(); ++index)
495 {
496 spectrum_.push_back(
497 {.id = {.sector = sector.charge, .index = index}, .singular_value = sector.host_singular_values[index]});
498 }
499 }
500 std::ranges::sort(spectrum_, [](auto const& lhs, auto const& rhs) {
501 if (lhs.singular_value != rhs.singular_value) return lhs.singular_value > rhs.singular_value;
502 if (lhs.id.sector.raw_code() != rhs.id.sector.raw_code())
503 return lhs.id.sector.raw_code() < rhs.id.sector.raw_code();
504 return lhs.id.index < rhs.id.index;
505 });
506 }
507
508 public:
510 auto symmetry() const noexcept -> Symmetry { return symmetry_; }
511
513 auto domain() const noexcept -> domain_type const& { return domain_; }
514
516 auto codomain() const noexcept -> codomain_type const& { return codomain_; }
517
519 auto options() const noexcept -> linalg::SvdOptions { return options_; }
520
522 auto sectors() const noexcept -> std::span<Sector const> { return sectors_; }
523
525 auto spectrum() const noexcept -> std::span<BlockSvdState<real_type> const> { return spectrum_; }
526
529 {
530 std::vector<BlockSvdStateId> result;
531 for (Sector const& sector : sectors_)
532 {
533 auto const rank = static_cast<std::size_t>(sector.singular_values.extent(0));
534 auto const extent = static_cast<std::size_t>(sector.left_singular_vectors.extent(1));
535 for (std::size_t index = rank; index < extent; ++index)
536 result.push_back({.sector = sector.charge, .index = index});
537 }
538 return BlockSvdVectorSelection{std::move(result)};
539 }
540
543 {
544 std::vector<BlockSvdStateId> result;
545 for (Sector const& sector : sectors_)
546 {
547 auto const rank = static_cast<std::size_t>(sector.singular_values.extent(0));
548 auto const extent = static_cast<std::size_t>(sector.right_singular_vectors_adjoint.extent(0));
549 for (std::size_t index = rank; index < extent; ++index)
550 result.push_back({.sector = sector.charge, .index = index});
551 }
552 return BlockSvdVectorSelection{std::move(result)};
553 }
554
555 private:
556 Symmetry symmetry_;
557 domain_type domain_;
558 codomain_type codomain_;
559 linalg::SvdOptions options_;
560 std::vector<Sector> sectors_;
561 std::vector<BlockSvdState<real_type>> spectrum_;
562 [[no_unique_address]] detail::BlockSvdAllocationContextBinding<allocation_context_type> allocation_context_;
563};
564
581template <ImmediateBlockTensorView Tensor, class Measurements, class Event, class Factorizer>
582 requires uni20::LapackScalar<block_tensor_value_t<Tensor>> && performance::BatchMeasurementPolicy<Measurements, Event>
583[[nodiscard]] auto block_svd(Tensor const& tensor, linalg::SvdOptions options, Measurements& measurements,
584 Event batch_event, Factorizer&& factorizer)
585{
587 using block_execution_policy = typename block_tensor_type_t<Tensor>::storage_policy::block_execution_policy;
588 using provider_result_type =
589 std::invoke_result_t<Factorizer&, ColumnMajorTensor<scalar_type, 2> const&, linalg::SvdOptions>;
590 using factorization_type = decltype(detail::retain_host_svd_result(std::declval<provider_result_type>()));
591 static_assert(std::same_as<typename factorization_type::left_tensor_type,
592 typename factorization_type::right_adjoint_tensor_type>,
593 "block SVD requires one matrix storage type for both provider factors");
594 using decomposition_type =
596 block_execution_policy, typename factorization_type::left_tensor_type,
597 typename factorization_type::singular_value_tensor_type>;
598 using sector_type = typename decomposition_type::Sector;
599 auto domain_sectors =
600 detail::group_svd_boundary_fragments(detail::make_svd_boundary_fragments(tensor.symmetry(), tensor.domain()));
601 auto codomain_sectors =
602 detail::group_svd_boundary_fragments(detail::make_svd_boundary_fragments(tensor.symmetry(), tensor.codomain()));
603
604 using domain_sector_type = typename decltype(domain_sectors)::value_type;
605 using codomain_sector_type = typename decltype(codomain_sectors)::value_type;
606 struct SectorInput
607 {
608 domain_sector_type domain;
609 codomain_sector_type codomain;
610 };
611
612 std::vector<SectorInput> sector_inputs;
613 std::size_t domain_sector = 0;
614 std::size_t codomain_sector = 0;
615 while (domain_sector < domain_sectors.size() || codomain_sector < codomain_sectors.size())
616 {
617 bool const has_domain = domain_sector < domain_sectors.size();
618 bool const has_codomain = codomain_sector < codomain_sectors.size();
619 if (has_domain && (!has_codomain || domain_sectors[domain_sector].charge.raw_code() <
620 codomain_sectors[codomain_sector].charge.raw_code()))
621 {
623 {
624 auto& domain = domain_sectors[domain_sector];
625 QNum const charge = domain.charge;
626 sector_inputs.push_back({.domain = std::move(domain), .codomain = codomain_sector_type{.charge = charge}});
627 }
628 ++domain_sector;
629 continue;
630 }
631 if (has_codomain && (!has_domain || codomain_sectors[codomain_sector].charge.raw_code() <
632 domain_sectors[domain_sector].charge.raw_code()))
633 {
634 if (options.left == linalg::SvdVectorExtent::Full)
635 {
636 auto& codomain = codomain_sectors[codomain_sector];
637 QNum const charge = codomain.charge;
638 sector_inputs.push_back({.domain = domain_sector_type{.charge = charge}, .codomain = std::move(codomain)});
639 }
640 ++codomain_sector;
641 continue;
642 }
643
644 sector_inputs.push_back(
645 {.domain = std::move(domain_sectors[domain_sector]), .codomain = std::move(codomain_sectors[codomain_sector])});
646 ++domain_sector;
647 ++codomain_sector;
648 }
649
650 std::vector<std::optional<sector_type>> sector_results(sector_inputs.size());
651 auto factorize_sector = [&](std::size_t sector) {
652 auto& domain = sector_inputs[sector].domain;
653 auto& codomain = sector_inputs[sector].codomain;
654 // Missing stored blocks remain zero in the assembled sector.
655 ColumnMajorTensor<scalar_type, 2> matrix(static_cast<uni20::index_type>(codomain.flattened_extent),
656 static_cast<uni20::index_type>(domain.flattened_extent));
657 std::vector<typename decomposition_type::source_key_type> stored_source_keys;
658 for (auto const& domain_fragment : domain.fragments)
659 {
660 for (auto const& codomain_fragment : codomain.fragments)
661 {
662 auto const key_coordinates =
663 detail::concatenate_coordinates(domain_fragment.coordinates, codomain_fragment.coordinates);
664 typename decomposition_type::source_key_type const key{key_coordinates};
665 auto const found = std::ranges::lower_bound(tensor.stored_keys(), key);
666 if (found == tensor.stored_keys().end() || *found != key) continue;
667 auto const ordinal = static_cast<std::size_t>(found - tensor.stored_keys().begin());
668 auto block = tensor.block_by_ordinal(ordinal);
669 stored_source_keys.push_back(key);
670
671 for (std::size_t column = 0; column < domain_fragment.flattened_extent; ++column)
672 {
673 auto const domain_indices = detail::column_major_indices(column, domain_fragment.extents);
674 for (std::size_t row = 0; row < codomain_fragment.flattened_extent; ++row)
675 {
676 auto const codomain_indices = detail::column_major_indices(row, codomain_fragment.extents);
677 auto const block_indices = detail::concatenate_indices(domain_indices, codomain_indices);
678 matrix[static_cast<uni20::index_type>(codomain_fragment.sector_offset + row),
679 static_cast<uni20::index_type>(domain_fragment.sector_offset + column)] =
680 detail::block_element(block, block_indices);
681 }
682 }
683 }
684 }
685
686 auto exact = detail::retain_host_svd_result(std::invoke(factorizer, std::as_const(matrix), options));
687 sector_results[sector].emplace(
688 sector_type{.charge = domain.charge,
689 .domain_fragments = std::move(domain.fragments),
690 .codomain_fragments = std::move(codomain.fragments),
691 .stored_source_keys = std::move(stored_source_keys),
692 .left_singular_vectors = std::move(exact.left_singular_vectors),
693 .singular_values = std::move(exact.singular_values),
694 .host_singular_values = std::move(exact.host_singular_values),
695 .right_singular_vectors_adjoint = std::move(exact.right_singular_vectors_adjoint)});
696 };
697
698 std::vector<std::size_t> sector_order(sector_inputs.size());
699 std::iota(sector_order.begin(), sector_order.end(), 0);
700 auto estimated_cost = [&](std::size_t sector) {
701 std::size_t const rows = sector_inputs[sector].codomain.flattened_extent;
702 std::size_t const columns = sector_inputs[sector].domain.flattened_extent;
703 return rows * columns * std::min(rows, columns);
704 };
705 std::ranges::stable_sort(sector_order,
706 [&](std::size_t lhs, std::size_t rhs) { return estimated_cost(lhs) > estimated_cost(rhs); });
707
709 measurements, batch_event, sector_order.size(),
710 [&](auto& function) {
711 detail::execute_svd_sector_batch(block_execution_policy{}, sector_order.size(), function);
712 },
713 [&](std::size_t task) { factorize_sector(sector_order[task]); });
714
715 std::vector<sector_type> sectors;
716 sectors.reserve(sector_results.size());
717 for (auto& sector : sector_results)
718 sectors.push_back(std::move(sector).value());
719
720 return decomposition_type{tensor.symmetry(), tensor.domain(), tensor.codomain(), options, std::move(sectors)};
721}
722
724template <ImmediateBlockTensorView Tensor, class Measurements, class Event>
725 requires uni20::LapackScalar<block_tensor_value_t<Tensor>> && performance::BatchMeasurementPolicy<Measurements, Event>
726[[nodiscard]] auto block_svd(Tensor const& tensor, linalg::SvdOptions options, Measurements& measurements,
727 Event batch_event)
728{
729 auto factorizer = [](auto const& matrix, linalg::SvdOptions sector_options) {
730 return linalg::svd(matrix, sector_options);
731 };
732 return block_svd(tensor, options, measurements, batch_event, factorizer);
733}
734
741template <ImmediateBlockTensorView Tensor>
743[[nodiscard]] auto block_svd(Tensor const& tensor, linalg::SvdOptions options = {})
744{
745 performance::NoMeasurements measurements;
746 return block_svd(tensor, options, measurements, nullptr);
747}
748
749#if UNI20_BACKEND_CUSOLVER
750namespace detail
751{
752template <std::size_t Rank, MutableTensorView Tensor>
753 requires std::same_as<tensor_storage_policy_t<Tensor>, CudaStorage>
754[[nodiscard]] auto make_cuda_strided_view(Tensor& tensor, std::array<std::size_t, Rank> const& extents,
755 std::array<std::size_t, Rank> const& strides, std::size_t offset)
756{
757 auto mutable_base = mdspec_of(tensor);
758 auto const_base = mdspec_of(std::as_const(tensor));
759 using mutable_base_type = std::remove_cvref_t<decltype(mutable_base)>;
760 using const_base_type = std::remove_cvref_t<decltype(const_base)>;
761 using index_type = typename mutable_base_type::index_type;
762 using extents_type = stdex::dextents<index_type, Rank>;
763 using layout_type = stdex::layout_stride;
764 using mapping_type = typename layout_type::template mapping<extents_type>;
765 using mutable_mdspec_type =
766 mdspec<typename mutable_base_type::element_type, extents_type, layout_type,
767 typename mutable_base_type::accessor_type, typename mutable_base_type::data_descriptor_type>;
768 using const_mdspec_type =
769 mdspec<typename const_base_type::element_type, extents_type, layout_type, typename const_base_type::accessor_type,
770 typename const_base_type::data_descriptor_type>;
771
772 auto view_extents = [&]<std::size_t... Axis>(std::index_sequence<Axis...>) {
773 return extents_type{static_cast<index_type>(extents[Axis])...};
774 }(std::make_index_sequence<Rank>{});
775 std::array<index_type, Rank> view_strides{};
776 for (std::size_t axis = 0; axis < Rank; ++axis)
777 view_strides[axis] = static_cast<index_type>(strides[axis]);
778 mapping_type mapping(view_extents, view_strides);
779 return MdspecTensorView<mutable_mdspec_type, const_mdspec_type, CudaStorage>{
780 mutable_mdspec_type{mutable_base.data_descriptor().offset_by(offset), mapping, mutable_base.accessor()},
781 const_mdspec_type{const_base.data_descriptor().offset_by(offset), mapping, const_base.accessor()}};
782}
783
784template <std::size_t Rank, TensorView Tensor>
785 requires std::same_as<tensor_storage_policy_t<Tensor>, CudaStorage>
786[[nodiscard]] auto make_const_cuda_strided_view(Tensor const& tensor, std::array<std::size_t, Rank> const& extents,
787 std::array<std::size_t, Rank> const& strides, std::size_t offset)
788{
789 auto base = mdspec_of(tensor);
790 using base_type = std::remove_cvref_t<decltype(base)>;
791 using index_type = typename base_type::index_type;
792 using extents_type = stdex::dextents<index_type, Rank>;
793 using layout_type = stdex::layout_stride;
794 using mapping_type = typename layout_type::template mapping<extents_type>;
795 using mdspec_type = mdspec<typename base_type::element_type, extents_type, layout_type,
796 typename base_type::accessor_type, typename base_type::data_descriptor_type>;
797
798 auto view_extents = [&]<std::size_t... Axis>(std::index_sequence<Axis...>) {
799 return extents_type{static_cast<index_type>(extents[Axis])...};
800 }(std::make_index_sequence<Rank>{});
801 std::array<index_type, Rank> view_strides{};
802 for (std::size_t axis = 0; axis < Rank; ++axis)
803 view_strides[axis] = static_cast<index_type>(strides[axis]);
804 mapping_type mapping(view_extents, view_strides);
805 mdspec_type view{base.data_descriptor().offset_by(offset), mapping, base.accessor()};
806 return MdspecTensorView<mdspec_type, mdspec_type, CudaStorage>{view, view};
807}
808
809template <MutableRankedTensorView<2> Output, RankedTensorView<2> Input>
810 requires std::same_as<tensor_storage_policy_t<Output>, CudaStorage> &&
811 std::same_as<tensor_storage_policy_t<Input>, CudaStorage>
812void copy_transposed_cuda_matrix(Output& output, Input const& input)
813{
814 ERROR_IF(output.extent(0) != input.extent(1) || output.extent(1) != input.extent(0),
815 "CUDA transpose copy requires reversed matrix extents");
816 auto input_spec = mdspec_of(input);
817 auto transposed = make_const_cuda_strided_view<2>(
818 input, {static_cast<std::size_t>(input.extent(1)), static_cast<std::size_t>(input.extent(0))},
819 {static_cast<std::size_t>(input_spec.stride(1)), static_cast<std::size_t>(input_spec.stride(0))}, 0);
820 uni20::copy(output, transposed);
821}
822
823template <class Scalar> void set_cuda_identity(CudaMatrix<Scalar>& matrix)
824{
825 uni20::fill(matrix, Scalar{});
826 std::size_t const diagonal_extent =
827 std::min(static_cast<std::size_t>(matrix.extent(0)), static_cast<std::size_t>(matrix.extent(1)));
828 if (diagonal_extent == 0) return;
829
830 auto matrix_spec = mdspec_of(matrix);
831 auto diagonal = make_cuda_strided_view<1>(
832 matrix, {diagonal_extent},
833 {static_cast<std::size_t>(matrix_spec.stride(0)) + static_cast<std::size_t>(matrix_spec.stride(1))}, 0);
834 uni20::fill(diagonal, Scalar{1});
835}
836
837template <BlockTensorView Tensor, class DomainSector, class CodomainSector>
838[[nodiscard]] auto assemble_cuda_svd_sector(cuda::DeviceResources& resources, Tensor const& tensor,
839 DomainSector const& domain, CodomainSector const& codomain,
840 std::vector<typename block_tensor_type_t<Tensor>::key_type>& stored_keys)
841{
842 using scalar_type = block_tensor_value_t<Tensor>;
843 using source_key_type = typename block_tensor_type_t<Tensor>::key_type;
844 // Missing stored blocks remain zero in the assembled sector.
845 CudaMatrix<scalar_type> matrix(resources, static_cast<uni20::index_type>(codomain.flattened_extent),
846 static_cast<uni20::index_type>(domain.flattened_extent));
847
848 for (auto const& domain_fragment : domain.fragments)
849 {
850 for (auto const& codomain_fragment : codomain.fragments)
851 {
852 auto const key_coordinates = concatenate_coordinates(domain_fragment.coordinates, codomain_fragment.coordinates);
853 source_key_type const key{key_coordinates};
854 auto const found = std::ranges::lower_bound(tensor.stored_keys(), key);
855 if (found == tensor.stored_keys().end() || *found != key) continue;
856 auto const ordinal = static_cast<std::size_t>(found - tensor.stored_keys().begin());
857 auto block = tensor.block_by_ordinal(ordinal);
858 ERROR_IF(static_cast<std::size_t>(block.extent(0)) != domain_fragment.flattened_extent ||
859 static_cast<std::size_t>(block.extent(1)) != codomain_fragment.flattened_extent,
860 "CUDA block SVD currently requires one dense axis on each matrix boundary");
861 stored_keys.push_back(key);
862
863 auto matrix_spec = mdspec_of(matrix);
864 auto block_spec = mdspec_of(block);
865 auto target = make_cuda_strided_view<2>(
866 matrix, {codomain_fragment.flattened_extent, domain_fragment.flattened_extent},
867 {static_cast<std::size_t>(matrix_spec.stride(0)), static_cast<std::size_t>(matrix_spec.stride(1))},
868 codomain_fragment.sector_offset * static_cast<std::size_t>(matrix_spec.stride(0)) +
869 domain_fragment.sector_offset * static_cast<std::size_t>(matrix_spec.stride(1)));
870 auto transposed_block = make_const_cuda_strided_view<2>(
871 block, {codomain_fragment.flattened_extent, domain_fragment.flattened_extent},
872 {static_cast<std::size_t>(block_spec.stride(1)), static_cast<std::size_t>(block_spec.stride(0))}, 0);
873 uni20::copy(target, transposed_block);
874 }
875 }
876 return matrix;
877}
878
879template <class Scalar>
880[[nodiscard]] auto cuda_svd_resident_result(cuda::DeviceResources& resources, CudaMatrix<Scalar>& matrix_work,
881 linalg::SvdOptions options)
882{
883 std::size_t const rows = static_cast<std::size_t>(matrix_work.extent(0));
884 std::size_t const columns = static_cast<std::size_t>(matrix_work.extent(1));
885 std::size_t const rank = std::min(rows, columns);
886 std::size_t const left_columns = options.left == linalg::SvdVectorExtent::Full ? rows : rank;
887 std::size_t const right_rows = options.right == linalg::SvdVectorExtent::Full ? columns : rank;
888 CudaTensor<Scalar, 1> device_values(uninitialized, resources, rank);
889 CudaMatrix<Scalar> device_left(uninitialized, resources, rows, left_columns);
890 CudaMatrix<Scalar> device_right(uninitialized, resources, right_rows, columns);
891
892 if (rank == 0)
893 {
894 set_cuda_identity(device_left);
895 set_cuda_identity(device_right);
896 return BlockSvdFactorizationResult<decltype(device_left), decltype(device_values), decltype(device_right)>{
897 .left_singular_vectors = std::move(device_left),
898 .singular_values = std::move(device_values),
899 .host_singular_values = {},
900 .right_singular_vectors_adjoint = std::move(device_right)};
901 }
902
903 if (rows >= columns)
904 {
905 linalg::singular_value_decomposition(linalg::CusolverBackend{}, device_values, device_left, device_right,
906 matrix_work, options);
907 }
908 else
909 {
910 CudaMatrix<Scalar> transposed_work(uninitialized, resources, columns, rows);
911 copy_transposed_cuda_matrix(transposed_work, matrix_work);
912 CudaMatrix<Scalar> transposed_left(uninitialized, resources, columns,
913 options.right == linalg::SvdVectorExtent::Full ? columns : rank);
914 CudaMatrix<Scalar> transposed_right(uninitialized, resources,
915 options.left == linalg::SvdVectorExtent::Full ? rows : rank, rows);
916 linalg::singular_value_decomposition(linalg::CusolverBackend{}, device_values, transposed_left, transposed_right,
917 transposed_work, {.left = options.right, .right = options.left});
918 copy_transposed_cuda_matrix(device_left, transposed_right);
919 copy_transposed_cuda_matrix(device_right, transposed_left);
920 }
921
922 ColumnMajorTensor<Scalar, 1> values(uninitialized, rank);
923 uni20::copy(values, device_values);
924 std::vector<Scalar> host_values(rank);
925 for (std::size_t index = 0; index < rank; ++index)
926 host_values[index] = values[static_cast<uni20::index_type>(index)];
927 return BlockSvdFactorizationResult<decltype(device_left), decltype(device_values), decltype(device_right)>{
928 .left_singular_vectors = std::move(device_left),
929 .singular_values = std::move(device_values),
930 .host_singular_values = std::move(host_values),
931 .right_singular_vectors_adjoint = std::move(device_right)};
932}
933} // namespace detail
934
947template <BlockTensorView Tensor, class Measurements, class Event>
948 requires(!ImmediateBlockTensorView<Tensor>) &&
949 CompleteBlockStorage<typename block_tensor_type_t<Tensor>::storage_policy> &&
950 (block_tensor_type_t<Tensor>::dense_block_order() == 2) &&
951 linalg::detail::cusolver_backend::CusolverSvdScalar<block_tensor_value_t<Tensor>> &&
952 cuda::BufferMdspec<tensor_mdspec_t<block_tensor_const_block_t<Tensor>>> &&
953 requires(Tensor const& tensor) {
954 { tensor.allocation_context() } -> std::same_as<cuda::DeviceResources&>;
955 } && performance::BatchMeasurementPolicy<Measurements, Event>
956[[nodiscard]] auto block_svd(Tensor const& tensor, linalg::SvdOptions options, Measurements& measurements,
957 Event batch_event)
958{
959 using scalar_type = block_tensor_value_t<Tensor>;
960 using block_execution_policy = typename block_tensor_type_t<Tensor>::storage_policy::block_execution_policy;
961 using decomposition_type =
962 BlockSvdDecomposition<scalar_type, block_tensor_domain_t<Tensor>, block_tensor_codomain_t<Tensor>,
963 block_execution_policy, CudaMatrix<scalar_type>, CudaTensor<scalar_type, 1>,
964 cuda::DeviceResources>;
965 using sector_type = typename decomposition_type::Sector;
966
967 auto& resources = tensor.allocation_context();
968
969 auto domain_sectors =
970 detail::group_svd_boundary_fragments(detail::make_svd_boundary_fragments(tensor.symmetry(), tensor.domain()));
971 auto codomain_sectors =
972 detail::group_svd_boundary_fragments(detail::make_svd_boundary_fragments(tensor.symmetry(), tensor.codomain()));
973 using domain_sector_type = typename decltype(domain_sectors)::value_type;
974 using codomain_sector_type = typename decltype(codomain_sectors)::value_type;
975 struct SectorInput
976 {
977 domain_sector_type domain;
978 codomain_sector_type codomain;
979 };
980
981 std::vector<SectorInput> sector_inputs;
982 std::size_t domain_sector = 0;
983 std::size_t codomain_sector = 0;
984 while (domain_sector < domain_sectors.size() || codomain_sector < codomain_sectors.size())
985 {
986 bool const has_domain = domain_sector < domain_sectors.size();
987 bool const has_codomain = codomain_sector < codomain_sectors.size();
988 if (has_domain && (!has_codomain || domain_sectors[domain_sector].charge.raw_code() <
989 codomain_sectors[codomain_sector].charge.raw_code()))
990 {
991 if (options.right == linalg::SvdVectorExtent::Full)
992 {
993 auto& domain = domain_sectors[domain_sector];
994 QNum const charge = domain.charge;
995 sector_inputs.push_back({.domain = std::move(domain), .codomain = codomain_sector_type{.charge = charge}});
996 }
997 ++domain_sector;
998 continue;
999 }
1000 if (has_codomain && (!has_domain || codomain_sectors[codomain_sector].charge.raw_code() <
1001 domain_sectors[domain_sector].charge.raw_code()))
1002 {
1003 if (options.left == linalg::SvdVectorExtent::Full)
1004 {
1005 auto& codomain = codomain_sectors[codomain_sector];
1006 QNum const charge = codomain.charge;
1007 sector_inputs.push_back({.domain = domain_sector_type{.charge = charge}, .codomain = std::move(codomain)});
1008 }
1009 ++codomain_sector;
1010 continue;
1011 }
1012
1013 sector_inputs.push_back(
1014 {.domain = std::move(domain_sectors[domain_sector]), .codomain = std::move(codomain_sectors[codomain_sector])});
1015 ++domain_sector;
1016 ++codomain_sector;
1017 }
1018
1019 std::vector<std::optional<sector_type>> sector_results(sector_inputs.size());
1020 auto factorize_sector = [&](std::size_t sector) {
1021 auto& domain = sector_inputs[sector].domain;
1022 auto& codomain = sector_inputs[sector].codomain;
1023 std::vector<typename decomposition_type::source_key_type> stored_source_keys;
1024 auto matrix = detail::assemble_cuda_svd_sector(resources, tensor, domain, codomain, stored_source_keys);
1025 auto exact = detail::cuda_svd_resident_result(resources, matrix, options);
1026 sector_results[sector].emplace(
1027 sector_type{.charge = domain.charge,
1028 .domain_fragments = std::move(domain.fragments),
1029 .codomain_fragments = std::move(codomain.fragments),
1030 .stored_source_keys = std::move(stored_source_keys),
1031 .left_singular_vectors = std::move(exact.left_singular_vectors),
1032 .singular_values = std::move(exact.singular_values),
1033 .host_singular_values = std::move(exact.host_singular_values),
1034 .right_singular_vectors_adjoint = std::move(exact.right_singular_vectors_adjoint)});
1035 };
1036
1037 std::vector<std::size_t> sector_order(sector_inputs.size());
1038 std::iota(sector_order.begin(), sector_order.end(), 0);
1039 auto estimated_cost = [&](std::size_t sector) {
1040 std::size_t const rows = sector_inputs[sector].codomain.flattened_extent;
1041 std::size_t const columns = sector_inputs[sector].domain.flattened_extent;
1042 return rows * columns * std::min(rows, columns);
1043 };
1044 std::ranges::stable_sort(sector_order,
1045 [&](std::size_t lhs, std::size_t rhs) { return estimated_cost(lhs) > estimated_cost(rhs); });
1046 performance::measure_batch(
1047 measurements, batch_event, sector_order.size(),
1048 [&](auto& function) {
1049 detail::execute_svd_sector_batch(block_execution_policy{}, sector_order.size(), function);
1050 },
1051 [&](std::size_t task) { factorize_sector(sector_order[task]); });
1052
1053 std::vector<sector_type> sectors;
1054 sectors.reserve(sector_results.size());
1055 for (auto& sector : sector_results)
1056 sectors.push_back(std::move(sector).value());
1057 return decomposition_type{tensor.symmetry(), tensor.domain(), tensor.codomain(), options, std::move(sectors),
1058 resources};
1059}
1060
1062template <BlockTensorView Tensor>
1063 requires(!ImmediateBlockTensorView<Tensor>) &&
1064 CompleteBlockStorage<typename block_tensor_type_t<Tensor>::storage_policy> &&
1065 linalg::detail::cusolver_backend::CusolverSvdScalar<block_tensor_value_t<Tensor>> &&
1066 cuda::BufferMdspec<tensor_mdspec_t<block_tensor_const_block_t<Tensor>>>
1067[[nodiscard]] auto block_svd(Tensor const& tensor, linalg::SvdOptions options = {})
1068{
1069 performance::NoMeasurements measurements;
1070 return block_svd(tensor, options, measurements, nullptr);
1071}
1072#endif
1073
1075template <uni20::Real Real>
1076[[nodiscard]] auto make_svd_selection(std::span<BlockSvdState<Real> const> spectrum,
1077 std::span<BlockSvdStateId const> requested) -> BlockSvdSelection<Real>
1078{
1079 auto [canonical, selected] = detail::canonical_svd_selection(spectrum, requested);
1080 auto statistics = detail::summarize_svd_selection(spectrum, std::span<unsigned char const>{selected});
1081 return BlockSvdSelection<Real>{std::move(canonical), std::move(statistics)};
1082}
1083
1085template <uni20::Real Real>
1086[[nodiscard]] auto select_svd_states(std::span<BlockSvdState<Real> const> spectrum,
1088{
1089 std::vector<Real> values;
1090 values.reserve(spectrum.size());
1091 for (auto const& state : spectrum)
1092 values.push_back(state.singular_value);
1093 auto statistics = linalg::select_svd_truncation(std::span<Real const>{values}, policy);
1094
1095 std::vector<BlockSvdStateId> selected;
1096 selected.reserve(statistics.retained_rank);
1097 for (std::size_t ordinal = 0; ordinal < statistics.retained_rank; ++ordinal)
1098 selected.push_back(spectrum[ordinal].id);
1099 return BlockSvdSelection<Real>{std::move(selected), std::move(statistics)};
1100}
1101
1103template <uni20::Real Real>
1104[[nodiscard]] auto complement_svd_selection(std::span<BlockSvdState<Real> const> spectrum,
1106{
1107 auto [canonical, selected] = detail::canonical_svd_selection(spectrum, selection.state_ids());
1108 static_cast<void>(canonical);
1109 std::vector<BlockSvdStateId> complement;
1110 complement.reserve(spectrum.size() - selection.state_ids().size());
1111 for (std::size_t ordinal = 0; ordinal < spectrum.size(); ++ordinal)
1112 {
1113 if (!selected[ordinal]) complement.push_back(spectrum[ordinal].id);
1114 }
1115 return make_svd_selection(spectrum, std::span<BlockSvdStateId const>{complement});
1116}
1117
1118namespace detail
1119{
1120
1121template <class Decomposition>
1122using block_svd_factor_storage_t = tensor_storage_policy_t<typename Decomposition::matrix_type>;
1123
1124template <class Decomposition>
1125using block_svd_materialized_storage_t = PackedSparseBlockStorage<block_svd_factor_storage_t<Decomposition>>;
1126
1127template <class Decomposition> using block_svd_materialized_diagonal_storage_t = PackedDiagonalBlockStorage<>;
1128
1129template <class Decomposition> struct BlockSvdSelectionPlan
1130{
1131 BlockSpace bond_space;
1132 std::vector<std::vector<std::size_t>> sector_indices;
1133 std::vector<std::optional<std::size_t>> bond_coordinates;
1134};
1135
1136template <class Decomposition, class Extent>
1137auto make_block_svd_selection_plan(Decomposition const& decomposition, std::span<BlockSvdStateId const> state_ids,
1138 std::string bond_label, Extent&& extent) -> BlockSvdSelectionPlan<Decomposition>
1139{
1140 std::vector<std::vector<std::size_t>> sector_indices(decomposition.sectors().size());
1141 for (BlockSvdStateId const& id : state_ids)
1142 {
1143 auto const found =
1144 std::ranges::find_if(decomposition.sectors(), [&](auto const& sector) { return sector.charge == id.sector; });
1145 if (found == decomposition.sectors().end())
1146 throw std::invalid_argument("block-SVD materialization contains an unknown charge sector");
1147 auto const sector = static_cast<std::size_t>(found - decomposition.sectors().begin());
1148 if (id.index >= extent(*found)) throw std::invalid_argument("block-SVD state index is out of range");
1149 sector_indices[sector].push_back(id.index);
1150 }
1151
1152 std::vector<BlockSector> bond_sectors;
1153 std::vector<std::optional<std::size_t>> bond_coordinates(decomposition.sectors().size());
1154 for (std::size_t sector = 0; sector < decomposition.sectors().size(); ++sector)
1155 {
1156 auto& indices = sector_indices[sector];
1157 std::ranges::sort(indices);
1158 if (std::ranges::adjacent_find(indices) != indices.end())
1159 throw std::invalid_argument("block-SVD materialization contains a repeated state");
1160 if (indices.empty()) continue;
1161 bond_coordinates[sector] = bond_sectors.size();
1162 bond_sectors.push_back({.q = decomposition.sectors()[sector].charge, .dim = indices.size()});
1163 }
1164
1165 return {.bond_space = BlockSpace(decomposition.symmetry(), bond_sectors, std::move(bond_label)),
1166 .sector_indices = std::move(sector_indices),
1167 .bond_coordinates = std::move(bond_coordinates)};
1168}
1169
1170template <class Decomposition>
1171auto make_left_singular_vectors(Decomposition const& decomposition, BlockSvdSelectionPlan<Decomposition> const& plan,
1172 bool absorb_singular_values = false)
1173{
1174 using scalar_type = typename Decomposition::scalar_type;
1175 using output_type = BlockTensor<scalar_type, Domain<BlockSpace>, typename Decomposition::codomain_type,
1176 block_svd_materialized_storage_t<Decomposition>>;
1177 using key_type = typename output_type::key_type;
1178 constexpr std::size_t codomain_key_count = Decomposition::codomain_shape::key_coordinate_count;
1179 std::vector<key_type> keys;
1180 for (std::size_t sector = 0; sector < decomposition.sectors().size(); ++sector)
1181 {
1182 if (!plan.bond_coordinates[sector]) continue;
1183 for (auto const& fragment : decomposition.sectors()[sector].codomain_fragments)
1184 {
1185 std::array<std::size_t, 1 + codomain_key_count> coordinates{};
1186 coordinates[0] = *plan.bond_coordinates[sector];
1187 std::ranges::copy(fragment.coordinates, coordinates.begin() + 1);
1188 keys.emplace_back(coordinates);
1189 }
1190 }
1191
1192 auto result = [&] {
1193 if constexpr (requires {
1194 output_type(uninitialized, decomposition.symmetry(), Domain{plan.bond_space},
1195 decomposition.codomain(), keys, decomposition.allocation_context());
1196 })
1197 return output_type(uninitialized, decomposition.symmetry(), Domain{plan.bond_space}, decomposition.codomain(),
1198 std::move(keys), decomposition.allocation_context());
1199 else
1200 return output_type(uninitialized, decomposition.symmetry(), Domain{plan.bond_space}, decomposition.codomain(),
1201 std::move(keys));
1202 }();
1203 for (std::size_t sector = 0; sector < decomposition.sectors().size(); ++sector)
1204 {
1205 if (!plan.bond_coordinates[sector]) continue;
1206 auto const& source = decomposition.sectors()[sector];
1207 for (auto const& fragment : source.codomain_fragments)
1208 {
1209 std::array<std::size_t, 1 + codomain_key_count> coordinates{};
1210 coordinates[0] = *plan.bond_coordinates[sector];
1211 std::ranges::copy(fragment.coordinates, coordinates.begin() + 1);
1212 auto block = result.block(key_type{coordinates});
1213 if constexpr (ImmediateTensorView<typename Decomposition::matrix_type>)
1214 {
1215 for (std::size_t selected_index = 0; selected_index < plan.sector_indices[sector].size(); ++selected_index)
1216 {
1217 for (std::size_t row = 0; row < fragment.flattened_extent; ++row)
1218 {
1219 auto const codomain_indices = column_major_indices(row, fragment.extents);
1220 std::array<uni20::index_type, 1> const bond_index{static_cast<uni20::index_type>(selected_index)};
1221 auto const output_indices = concatenate_indices(bond_index, codomain_indices);
1222 auto value =
1223 source
1224 .left_singular_vectors[static_cast<uni20::index_type>(fragment.sector_offset + row),
1225 static_cast<uni20::index_type>(plan.sector_indices[sector][selected_index])];
1226 if (absorb_singular_values)
1227 value *= source.host_singular_values[plan.sector_indices[sector][selected_index]];
1228 block_element(block, output_indices) = value;
1229 }
1230 }
1231 }
1232#if UNI20_BACKEND_CUSOLVER
1233 else
1234 {
1235 static_assert(decltype(block)::rank() == 2,
1236 "CUDA block-SVD materialization currently requires one codomain dense axis");
1237 auto block_spec = mdspec_of(block);
1238 auto source_spec = mdspec_of(source.left_singular_vectors);
1239 for (std::size_t selected_index = 0; selected_index < plan.sector_indices[sector].size(); ++selected_index)
1240 {
1241 auto output_values = make_cuda_strided_view<1>(
1242 block, {fragment.flattened_extent}, {static_cast<std::size_t>(block_spec.stride(1))},
1243 selected_index * static_cast<std::size_t>(block_spec.stride(0)));
1244 auto input_values = make_const_cuda_strided_view<1>(
1245 source.left_singular_vectors, {fragment.flattened_extent},
1246 {static_cast<std::size_t>(source_spec.stride(0))},
1247 fragment.sector_offset * static_cast<std::size_t>(source_spec.stride(0)) +
1248 plan.sector_indices[sector][selected_index] * static_cast<std::size_t>(source_spec.stride(1)));
1249 if (absorb_singular_values)
1250 {
1252 output_values,
1253 linalg::scale<scalar_type>{source.host_singular_values[plan.sector_indices[sector][selected_index]]},
1254 input_values);
1255 }
1256 else
1257 {
1258 uni20::copy(output_values, input_values);
1259 }
1260 }
1261 }
1262#endif
1263 }
1264 }
1265 return result;
1266}
1267
1268template <class Decomposition>
1269auto make_right_singular_vectors_adjoint(Decomposition const& decomposition,
1270 BlockSvdSelectionPlan<Decomposition> const& plan,
1271 bool absorb_singular_values = false)
1272{
1273 using scalar_type = typename Decomposition::scalar_type;
1274 using output_type = BlockTensor<scalar_type, typename Decomposition::domain_type, Codomain<BlockSpace>,
1275 block_svd_materialized_storage_t<Decomposition>>;
1276 using key_type = typename output_type::key_type;
1277 constexpr std::size_t domain_key_count = Decomposition::domain_shape::key_coordinate_count;
1278
1279 std::vector<key_type> keys;
1280 for (std::size_t sector = 0; sector < decomposition.sectors().size(); ++sector)
1281 {
1282 if (!plan.bond_coordinates[sector]) continue;
1283 for (auto const& fragment : decomposition.sectors()[sector].domain_fragments)
1284 {
1285 std::array<std::size_t, domain_key_count + 1> coordinates{};
1286 std::ranges::copy(fragment.coordinates, coordinates.begin());
1287 coordinates[domain_key_count] = *plan.bond_coordinates[sector];
1288 keys.emplace_back(coordinates);
1289 }
1290 }
1291
1292 auto result = [&] {
1293 if constexpr (requires {
1294 output_type(uninitialized, decomposition.symmetry(), decomposition.domain(),
1295 Codomain{plan.bond_space}, keys, decomposition.allocation_context());
1296 })
1297 return output_type(uninitialized, decomposition.symmetry(), decomposition.domain(), Codomain{plan.bond_space},
1298 std::move(keys), decomposition.allocation_context());
1299 else
1300 return output_type(uninitialized, decomposition.symmetry(), decomposition.domain(), Codomain{plan.bond_space},
1301 std::move(keys));
1302 }();
1303 for (std::size_t sector = 0; sector < decomposition.sectors().size(); ++sector)
1304 {
1305 if (!plan.bond_coordinates[sector]) continue;
1306 auto const& source = decomposition.sectors()[sector];
1307 for (auto const& fragment : source.domain_fragments)
1308 {
1309 std::array<std::size_t, domain_key_count + 1> coordinates{};
1310 std::ranges::copy(fragment.coordinates, coordinates.begin());
1311 coordinates[domain_key_count] = *plan.bond_coordinates[sector];
1312 auto block = result.block(key_type{coordinates});
1313 if constexpr (ImmediateTensorView<typename Decomposition::matrix_type>)
1314 {
1315 for (std::size_t selected_index = 0; selected_index < plan.sector_indices[sector].size(); ++selected_index)
1316 {
1317 for (std::size_t column = 0; column < fragment.flattened_extent; ++column)
1318 {
1319 auto const domain_indices = column_major_indices(column, fragment.extents);
1320 std::array<uni20::index_type, 1> const bond_index{static_cast<uni20::index_type>(selected_index)};
1321 auto const output_indices = concatenate_indices(domain_indices, bond_index);
1322 auto value =
1323 source.right_singular_vectors_adjoint[static_cast<uni20::index_type>(
1324 plan.sector_indices[sector][selected_index]),
1325 static_cast<uni20::index_type>(fragment.sector_offset + column)];
1326 if (absorb_singular_values)
1327 value *= source.host_singular_values[plan.sector_indices[sector][selected_index]];
1328 block_element(block, output_indices) = value;
1329 }
1330 }
1331 }
1332#if UNI20_BACKEND_CUSOLVER
1333 else
1334 {
1335 static_assert(decltype(block)::rank() == 2,
1336 "CUDA block-SVD materialization currently requires one domain dense axis");
1337 auto block_spec = mdspec_of(block);
1338 auto source_spec = mdspec_of(source.right_singular_vectors_adjoint);
1339 for (std::size_t selected_index = 0; selected_index < plan.sector_indices[sector].size(); ++selected_index)
1340 {
1341 auto output_values = make_cuda_strided_view<1>(
1342 block, {fragment.flattened_extent}, {static_cast<std::size_t>(block_spec.stride(0))},
1343 selected_index * static_cast<std::size_t>(block_spec.stride(1)));
1344 auto input_values = make_const_cuda_strided_view<1>(
1345 source.right_singular_vectors_adjoint, {fragment.flattened_extent},
1346 {static_cast<std::size_t>(source_spec.stride(1))},
1347 plan.sector_indices[sector][selected_index] * static_cast<std::size_t>(source_spec.stride(0)) +
1348 fragment.sector_offset * static_cast<std::size_t>(source_spec.stride(1)));
1349 if (absorb_singular_values)
1350 {
1352 output_values,
1353 linalg::scale<scalar_type>{source.host_singular_values[plan.sector_indices[sector][selected_index]]},
1354 input_values);
1355 }
1356 else
1357 {
1358 uni20::copy(output_values, input_values);
1359 }
1360 }
1361 }
1362#endif
1363 }
1364 }
1365 return result;
1366}
1367
1368template <class Decomposition>
1369auto make_block_singular_values(Decomposition const& decomposition, BlockSvdSelectionPlan<Decomposition> const& plan)
1370{
1371 using real_type = typename Decomposition::real_type;
1372 using output_type = BlockTensor<real_type, Domain<BlockSpace>, Codomain<BlockSpace>,
1373 block_svd_materialized_diagonal_storage_t<Decomposition>>;
1374 using key_type = typename output_type::key_type;
1375 std::vector<key_type> keys;
1376 keys.reserve(plan.bond_space.size());
1377 for (std::size_t sector = 0; sector < decomposition.sectors().size(); ++sector)
1378 {
1379 if (!plan.bond_coordinates[sector]) continue;
1380 keys.emplace_back(std::array<std::size_t, 2>{*plan.bond_coordinates[sector], *plan.bond_coordinates[sector]});
1381 }
1382
1383 output_type result(uninitialized, decomposition.symmetry(), Domain{plan.bond_space}, Codomain{plan.bond_space},
1384 std::move(keys));
1385 for (std::size_t sector = 0; sector < decomposition.sectors().size(); ++sector)
1386 {
1387 if (!plan.bond_coordinates[sector]) continue;
1388 key_type const key{std::array<std::size_t, 2>{*plan.bond_coordinates[sector], *plan.bond_coordinates[sector]}};
1389 auto values = result.diagonal_values(key);
1390 for (std::size_t index = 0; index < plan.sector_indices[sector].size(); ++index)
1391 {
1392 values[index] = decomposition.sectors()[sector].host_singular_values[plan.sector_indices[sector][index]];
1393 }
1394 }
1395 return result;
1396}
1397
1398template <class Decomposition>
1399[[nodiscard]] auto
1400materialize_svd_with_absorption(Decomposition const& decomposition,
1401 BlockSvdSelection<typename Decomposition::real_type> const& selection,
1402 BlockSvdMaterializationOptions options, bool absorb_left, bool absorb_right)
1403{
1404 auto [state_ids, selected] = canonical_svd_selection(decomposition.spectrum(), selection.state_ids());
1405 auto truncation = summarize_svd_selection(decomposition.spectrum(), std::span<unsigned char const>{selected});
1406 auto plan = make_block_svd_selection_plan(
1407 decomposition, state_ids, std::move(options.bond_label),
1408 [](auto const& sector) { return static_cast<std::size_t>(sector.singular_values.extent(0)); });
1409 auto left = make_left_singular_vectors(decomposition, plan, absorb_left);
1410 auto values = make_block_singular_values(decomposition, plan);
1411 auto right = make_right_singular_vectors_adjoint(decomposition, plan, absorb_right);
1412 return BlockSvdMaterialization<decltype(left), decltype(values), decltype(right),
1413 linalg::SvdTruncationInfo<typename Decomposition::real_type>>{
1414 .left_singular_vectors = std::move(left),
1415 .singular_values = std::move(values),
1416 .right_singular_vectors_adjoint = std::move(right),
1417 .truncation = std::move(truncation)};
1418}
1419
1420} // namespace detail
1421
1427template <class Decomposition>
1428[[nodiscard]] auto materialize_svd(Decomposition const& decomposition,
1430 BlockSvdMaterializationOptions options = {})
1431{
1432 return detail::materialize_svd_with_absorption(decomposition, selection, std::move(options), false, false);
1433}
1434
1436template <class Decomposition, detail::BlockSvdStateSelection Selection>
1437[[nodiscard]] auto materialize_left_singular_vectors(Decomposition const& decomposition, Selection const& selection,
1438 BlockSvdMaterializationOptions options = {})
1439{
1440 auto plan = detail::make_block_svd_selection_plan(
1441 decomposition, selection.state_ids(), std::move(options.bond_label),
1442 [](auto const& sector) { return static_cast<std::size_t>(sector.left_singular_vectors.extent(1)); });
1443 return detail::make_left_singular_vectors(decomposition, plan);
1444}
1445
1447template <class Decomposition, detail::BlockSvdStateSelection Selection>
1448[[nodiscard]] auto materialize_right_singular_vectors_adjoint(Decomposition const& decomposition,
1449 Selection const& selection,
1450 BlockSvdMaterializationOptions options = {})
1451{
1452 auto plan = detail::make_block_svd_selection_plan(
1453 decomposition, selection.state_ids(), std::move(options.bond_label),
1454 [](auto const& sector) { return static_cast<std::size_t>(sector.right_singular_vectors_adjoint.extent(0)); });
1455 return detail::make_right_singular_vectors_adjoint(decomposition, plan);
1456}
1457
1458} // namespace uni20
AsyncTask square(ReadBuffer< int > in, WriteBuffer< int > out)
Definition async_tbb_reduction_example.cpp:39
Defines the first bosonic abelian BlockTensor container.
Concepts and associated types for BlockTensor-level views.
Defines structure-preserving linear operations on BlockTensor values.
Opaque logical block-selection coordinates in boundary order.
Definition block_key.hpp:20
Intermediate per-sector block SVD retaining factors and logical basis maps.
Definition block_tensor_svd.hpp:425
Scalar scalar_type
Definition block_tensor_svd.hpp:427
SingularValueTensor singular_value_tensor_type
Definition block_tensor_svd.hpp:440
auto options() const noexcept -> linalg::SvdOptions
Return exact-SVD vector extent options used by every sector.
Definition block_tensor_svd.hpp:519
auto spectrum() const noexcept -> std::span< BlockSvdState< real_type > const >
Return paired singular states in deterministic descending order.
Definition block_tensor_svd.hpp:525
DomainType domain_type
Definition block_tensor_svd.hpp:429
detail::SvdBoundaryShape< codomain_type > codomain_shape
Definition block_tensor_svd.hpp:433
CodomainType codomain_type
Definition block_tensor_svd.hpp:430
auto symmetry() const noexcept -> Symmetry
Return the symmetry shared by the input and every sector.
Definition block_tensor_svd.hpp:510
detail::SvdBoundaryFragment< codomain_shape::key_coordinate_count, codomain_shape::dense_block_order > codomain_fragment_type
Definition block_tensor_svd.hpp:437
auto sectors() const noexcept -> std::span< Sector const >
Return charge sectors in canonical charge order.
Definition block_tensor_svd.hpp:522
BlockSvdDecomposition(Symmetry symmetry, domain_type domain, codomain_type codomain, linalg::SvdOptions options, std::vector< Sector > sectors, Context &allocation_context)
Construct a completed decomposition retaining an explicit factor allocation context.
Definition block_tensor_svd.hpp:471
auto left_null_space() const -> BlockSvdVectorSelection
Return unpaired full left vectors in the dimensional-excess null space.
Definition block_tensor_svd.hpp:528
decltype(auto) allocation_context() const noexcept
Return the factor allocation context retained from the source tensor.
Definition block_tensor_svd.hpp:481
AllocationContext allocation_context_type
Definition block_tensor_svd.hpp:441
uni20::make_real_t< scalar_type > real_type
Definition block_tensor_svd.hpp:428
detail::SvdBoundaryFragment< domain_shape::key_coordinate_count, domain_shape::dense_block_order > domain_fragment_type
Definition block_tensor_svd.hpp:435
auto codomain() const noexcept -> codomain_type const &
Return the original codomain boundary.
Definition block_tensor_svd.hpp:516
detail::SvdBoundaryShape< domain_type > domain_shape
Definition block_tensor_svd.hpp:432
BlockSvdDecomposition(Symmetry symmetry, domain_type domain, codomain_type codomain, linalg::SvdOptions options, std::vector< Sector > sectors)
Construct a completed decomposition from sector provider results.
Definition block_tensor_svd.hpp:460
auto domain() const noexcept -> domain_type const &
Return the original domain boundary.
Definition block_tensor_svd.hpp:513
MatrixTensor matrix_type
Definition block_tensor_svd.hpp:439
auto right_null_space() const -> BlockSvdVectorSelection
Return unpaired full right vectors in the dimensional-excess null space.
Definition block_tensor_svd.hpp:542
BlockExecutionPolicy block_execution_policy
Definition block_tensor_svd.hpp:431
Arbitrary paired-state selection from a block-SVD spectrum.
Definition block_tensor_svd.hpp:92
auto state_ids() const noexcept -> std::span< BlockSvdStateId const >
Return selected state identities in global spectrum order.
Definition block_tensor_svd.hpp:95
friend auto select_svd_states(std::span< BlockSvdState< OtherReal > const > spectrum, linalg::SvdTruncationPolicy< OtherReal > const &policy) -> BlockSvdSelection< OtherReal >
friend auto make_svd_selection(std::span< BlockSvdState< OtherReal > const > spectrum, std::span< BlockSvdStateId const > requested) -> BlockSvdSelection< OtherReal >
auto truncation() const noexcept -> linalg::SvdTruncationInfo< Real > const &
Return statistics for this exact state set.
Definition block_tensor_svd.hpp:98
Selection of singular vectors which need not form paired triplets.
Definition block_tensor_svd.hpp:118
auto state_ids() const noexcept -> std::span< BlockSvdStateId const >
Return selected vector identities in canonical sector order.
Definition block_tensor_svd.hpp:124
BlockSvdVectorSelection(std::vector< BlockSvdStateId > state_ids)
Construct from charge-sector vector identities.
Definition block_tensor_svd.hpp:121
One irrep label together with the symmetry context needed to interpret it.
Definition qnum.hpp:29
Public handle to a canonicalized direct-product symmetry specification.
Definition symmetry.hpp:24
General-purpose owning tensor with immediate or deferred mdspan metadata.
Definition basic_tensor.hpp:109
Concept for scalar types with a configured dense LAPACK backend.
Definition scalar_concepts.hpp:147
Concept for real scalar types (float, double, etc.).
Definition scalar_concepts.hpp:69
Concept for numeric scalar types (integer, real, or complex).
Definition scalar_concepts.hpp:81
locale_t value
Definition data_table.cpp:49
Coroutine scheduler for AsyncTask.
Named backend-neutral function objects for elementwise transforms.
Domain
MKL computation domains accepted by domain-specific thread controls.
Definition backend_mkl.hpp:35
typename make_real< T >::type make_real_t
Alias for the underlying real-valued type of T.
Definition scalar_traits.hpp:168
constexpr bool isfinite(I const &x) noexcept
Returns whether an integer scalar is finite.
Definition math.hpp:121
Scalar math helper utilities.
Concrete tensor-level views over owned mdspan or mdspec metadata.
Definition qnum.hpp:403
int columns(std::FILE *stream)
Return the terminal column count associated with a C stream.
Definition terminal.cpp:493
std::pair< int, int > size(std::FILE *stream)
Return the terminal size associated with a C stream.
Definition terminal.cpp:504
int rows(std::FILE *stream)
Return the terminal row count associated with a C stream.
Definition terminal.cpp:482
void execute_batch(std::size_t size, Function &&function)
Execute a synchronous lightweight batch on the global scheduler.
Definition debug_scheduler.hpp:245
auto values(Tensor &tensor)
Definition packed_matrix.hpp:29
auto singular_values(BackendSelector selector, async::Async< MatrixTensor > const &matrix, std::optional< Precision > requested={})
Schedule preserving exact singular values through an explicit selector.
Definition async/svd.hpp:416
void measure_batch(Measurements &measurements, Event event, std::size_t size, Executor &&executor, Function &&function)
Execute a synchronous batch with compile-time-selected timing detail.
Definition performance_measurements.hpp:354
typename optional_traits< T >::value_type value_type
Definition data_table.hpp:42
Definition async/async.hpp:26
auto materialize_left_singular_vectors(Decomposition const &decomposition, Selection const &selection, BlockSvdMaterializationOptions options={})
Materialize selected left singular vectors, including full null-space vectors.
Definition block_tensor_svd.hpp:1437
std::remove_cvref_t< Tensor > block_tensor_type_t
Cvref-stripped BlockTensor-level object type.
Definition block_tensor_concepts.hpp:22
typename block_tensor_type_t< Tensor >::value_type block_tensor_value_t
Scalar value type exposed by a BlockTensor-level object.
Definition block_tensor_concepts.hpp:25
Codomain(Spaces &&...) -> Codomain< std::remove_cvref_t< Spaces >... >
Deduce a codomain's concrete value types.
auto select_svd_states(std::span< BlockSvdState< Real > const > spectrum, linalg::SvdTruncationPolicy< Real > const &policy={}) -> BlockSvdSelection< Real >
Apply the standard truncation policy to a globally sorted block-SVD spectrum.
Definition block_tensor_svd.hpp:1086
constexpr decltype(auto) mdspec_of(Tensor &tensor) noexcept(noexcept(tensor.mdspec()))
Return a tensor's immediate or descriptor-backed multidimensional metadata.
Definition tensor/concepts.hpp:50
auto materialize_right_singular_vectors_adjoint(Decomposition const &decomposition, Selection const &selection, BlockSvdMaterializationOptions options={})
Materialize selected right singular vectors adjoint, including full null-space vectors.
Definition block_tensor_svd.hpp:1448
void fill(BackendSelector selector, async::Async< OutputTensor > &output, Value &&value)
Fill an existing async Tensor without reading its old values.
Definition linalg/async/transform.hpp:205
auto make_svd_selection(std::span< BlockSvdState< Real > const > spectrum, std::span< BlockSvdStateId const > requested) -> BlockSvdSelection< Real >
Build an arbitrary paired-state selection and its exact statistics.
Definition block_tensor_svd.hpp:1076
void copy(BackendSelector selector, async::Async< OutputTensor > &output, async::Async< InputTensor > const &input)
Schedule a Tensor copy through an explicit backend selector.
Definition linalg/async/copy.hpp:174
auto block_svd(Tensor const &tensor, linalg::SvdOptions options, Measurements &measurements, Event batch_event, Factorizer &&factorizer)
Factorize an immediate BlockTensorView independently by conserved charge with performance measurement...
Definition block_tensor_svd.hpp:583
auto materialize_svd(Decomposition const &decomposition, BlockSvdSelection< typename Decomposition::real_type > const &selection, BlockSvdMaterializationOptions options={})
Materialize paired left, singular-value, and right-adjoint factors.
Definition block_tensor_svd.hpp:1428
std::ptrdiff_t index_type
Definition types.hpp:19
typename block_tensor_type_t< Tensor >::codomain_type block_tensor_codomain_t
Ordered codomain type exposed by a BlockTensor-level object.
Definition block_tensor_concepts.hpp:34
auto complement_svd_selection(std::span< BlockSvdState< Real > const > spectrum, BlockSvdSelection< Real > const &selection) -> BlockSvdSelection< Real >
Select every paired state not present in an existing selection.
Definition block_tensor_svd.hpp:1104
void assign_transform(BackendSelector selector, async::Async< OutputTensor > &output, Function &&function)
Schedule a nullary overwrite of an existing async Tensor.
Definition linalg/async/transform.hpp:188
Destructive and value-producing exact dense singular value decompositions.
Tensor-facing truncated dense singular value decompositions.
Compile-time-gated performance duration measurements.
std::size_t span
Definition presentation.cpp:2017
Provider result and logical fragment maps for one charge sector.
Definition block_tensor_svd.hpp:448
std::vector< domain_fragment_type > domain_fragments
Definition block_tensor_svd.hpp:450
matrix_type left_singular_vectors
Definition block_tensor_svd.hpp:453
singular_value_tensor_type singular_values
Definition block_tensor_svd.hpp:454
std::vector< source_key_type > stored_source_keys
Definition block_tensor_svd.hpp:452
std::vector< codomain_fragment_type > codomain_fragments
Definition block_tensor_svd.hpp:451
matrix_type right_singular_vectors_adjoint
Definition block_tensor_svd.hpp:456
std::vector< real_type > host_singular_values
Definition block_tensor_svd.hpp:455
QNum charge
Definition block_tensor_svd.hpp:449
Options controlling one selected block-SVD materialization.
Definition block_tensor_svd.hpp:132
Materialized paired factors and statistics for one SVD selection.
Definition block_tensor_svd.hpp:139
TruncationInfo truncation
Definition block_tensor_svd.hpp:143
SingularValues singular_values
Definition block_tensor_svd.hpp:141
RightAdjointTensor right_singular_vectors_adjoint
Definition block_tensor_svd.hpp:142
LeftTensor left_singular_vectors
Definition block_tensor_svd.hpp:140
Stable identity of one paired or side-specific block-SVD state.
Definition block_tensor_svd.hpp:66
std::size_t index
Definition block_tensor_svd.hpp:68
auto operator==(BlockSvdStateId const &) const -> bool=default
Compare sector and local index for exact equality.
QNum sector
Definition block_tensor_svd.hpp:67
One charge-labelled entry in a globally sorted singular spectrum.
Definition block_tensor_svd.hpp:76
BlockSvdStateId id
Definition block_tensor_svd.hpp:77
Real singular_value
Definition block_tensor_svd.hpp:78
Output-shape options for an exact singular value decomposition.
Definition operation_tags.hpp:255
SvdVectorExtent right
Definition operation_tags.hpp:257
SvdVectorExtent left
Definition operation_tags.hpp:256
Statistics describing a completed SVD truncation.
Definition ops/truncated_svd.hpp:44
Rank-selection policy for a truncated singular value decomposition.
Definition ops/truncated_svd.hpp:34
Trait to extract the scalar type from a type T.
Definition scalar_traits.hpp:93
Backend-dispatched tensor copies and inferred owning materialization.
Backend-dispatched elementwise overwrite and update operations.
General-purpose owning tensors with runtime extents.
#define ERROR_IF(cond,...)
Definition trace.hpp:389
int add(int a, int b)
Definition trace_example.cpp:11