46template <
class Context>
class BlockSvdAllocationContextBinding {
48 explicit BlockSvdAllocationContextBinding(Context& context) noexcept : context_(std::addressof(context)) {}
50 [[nodiscard]]
auto get() const noexcept -> Context& {
return *context_; }
56template <>
class BlockSvdAllocationContextBinding<void> {
58 BlockSvdAllocationContextBinding() noexcept = default;
83template <uni20::Real Real>
87template <uni20::Real Real>
98 auto truncation() const noexcept -> linalg::SvdTruncationInfo<
Real> const& {
return truncation_; }
102 : state_ids_(
std::move(state_ids)), truncation_(
std::move(truncation))
105 template <uni20::Real OtherReal>
109 template <uni20::Real OtherReal>
113 std::vector<BlockSvdStateId> state_ids_;
127 std::vector<BlockSvdStateId> state_ids_;
133 std::string bond_label = {};
137template <
class LeftTensor,
class SingularValues,
class RightAdjo
intTensor,
class TruncationInfo>
149template <
class Boundary>
struct SvdBoundaryShape;
151template <
template <
class...>
class Boundary,
class... Spaces>
152struct SvdBoundaryShape<Boundary<Spaces...>> : BoundaryBlockShape<Boundary<Spaces...>>
155template <std::
size_t KeyCoordinateCount, std::
size_t DenseOrder>
struct SvdBoundaryFragment
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;
164template <
class Fragment>
struct SvdBoundarySector
167 std::vector<Fragment> fragments = {};
168 std::size_t flattened_extent = 0;
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)
175 if constexpr (I == Boundary::size())
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));
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;
191 for (std::size_t coordinate = 0; coordinate < coordinate_count; ++coordinate)
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);
203template <
class Boundary>
auto make_svd_boundary_fragments(Symmetry symmetry, Boundary
const& boundary)
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);
212template <
class Fragment>
213auto group_svd_boundary_fragments(std::vector<Fragment> fragments) -> std::vector<SvdBoundarySector<Fragment>>
215 std::map<std::uint64_t, SvdBoundarySector<Fragment>> grouped;
216 for (
auto& fragment : fragments)
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));
226 std::vector<SvdBoundarySector<Fragment>> result;
227 result.reserve(grouped.size());
228 for (
auto& [code, sector] : grouped)
230 static_cast<void>(code);
231 result.push_back(std::move(sector));
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>
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));
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>
250 std::array<uni20::index_type, Rank> result{};
251 for (std::size_t axis = 0; axis < Rank; ++axis)
254 linear /= extents[axis];
259template <
class Block, std::
size_t Rank>
260decltype(
auto) block_element(Block&& block, std::array<uni20::index_type, Rank>
const& indices)
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>{});
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>
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));
278template <uni20::Real Real>
class SvdNonnegativeSum {
284 correction_ += (sum_ - next) +
value;
286 correction_ += (
value - next) + sum_;
290 auto value() const -> Real {
return sum_ + correction_; }
297inline auto same_svd_state_id(BlockSvdStateId
const& lhs, BlockSvdStateId
const& rhs) ->
bool {
return lhs == rhs; }
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>>
303 std::vector<unsigned char> selected(spectrum.size(), 0);
304 for (BlockSvdStateId
const&
id : requested)
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;
314 std::vector<BlockSvdStateId> canonical;
315 canonical.reserve(requested.size());
316 for (std::size_t ordinal = 0; ordinal < spectrum.size(); ++ordinal)
318 if (selected[ordinal]) canonical.push_back(spectrum[ordinal].
id);
320 return {std::move(canonical), std::move(selected)};
323template <uni20::Real Real>
324auto summarize_svd_selection(std::span<BlockSvdState<Real>
const> spectrum,
325 std::span<unsigned char const> selected) -> linalg::SvdTruncationInfo<Real>
328 if (!spectrum.empty()) scale = spectrum.front().singular_value;
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)
337 Real const value = spectrum[ordinal].singular_value;
340 throw std::runtime_error(
"block-SVD spectrum contains an invalid singular value");
344 if (selected[ordinal])
347 if (!smallest_retained ||
value < *smallest_retained) smallest_retained =
value;
349 else if (!largest_discarded ||
value > *largest_discarded)
351 largest_discarded =
value;
353 if (!selected[ordinal]) discarded.add(
square);
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};
365template <
class Selection>
366concept BlockSvdStateSelection =
requires(Selection
const& selection) {
367 { selection.state_ids() } -> std::same_as<std::span<BlockSvdStateId const>>;
370template <
class Function>
void execute_svd_sector_batch(SerialBlockExecution, std::size_t size, Function&& function)
372 for (std::size_t index = 0; index <
size; ++index)
376template <
class Function>
377void execute_svd_sector_batch(SchedulerBatchBlockExecution, std::size_t size, Function&& function)
382template <
class LeftTensor,
class SingularValueTensor,
class RightAdjo
intTensor>
struct BlockSvdFactorizationResult
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>;
389 left_tensor_type left_singular_vectors;
391 std::vector<real_type> host_singular_values;
392 right_adjoint_tensor_type right_singular_vectors_adjoint;
395template <
class Exact> [[nodiscard]]
auto retain_host_svd_result(Exact exact)
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)};
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>
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>;
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");
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))
466 this->build_spectrum();
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)
477 this->build_spectrum();
482 requires(!std::same_as<allocation_context_type, void>)
484 return allocation_context_.get();
488 void build_spectrum()
490 for (Sector
const& sector : sectors_)
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)
497 {.id = {.sector = sector.charge, .index = index}, .singular_value = sector.host_singular_values[index]});
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;
519 auto options() const noexcept -> linalg::SvdOptions {
return options_; }
530 std::vector<BlockSvdStateId> result;
531 for (
Sector const& sector : sectors_)
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});
544 std::vector<BlockSvdStateId> result;
545 for (
Sector const& sector : sectors_)
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});
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_;
581template <ImmediateBlockTensorView Tensor,
class Measurements,
class Event,
class Factorizer>
584 Event batch_event, Factorizer&& factorizer)
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()));
604 using domain_sector_type =
typename decltype(domain_sectors)::value_type;
605 using codomain_sector_type =
typename decltype(codomain_sectors)::value_type;
608 domain_sector_type domain;
609 codomain_sector_type codomain;
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())
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()))
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}});
631 if (has_codomain && (!has_domain || codomain_sectors[codomain_sector].charge.raw_code() <
632 domain_sectors[domain_sector].charge.raw_code()))
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)});
644 sector_inputs.push_back(
645 {.domain = std::move(domain_sectors[domain_sector]), .codomain = std::move(codomain_sectors[codomain_sector])});
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;
657 std::vector<typename decomposition_type::source_key_type> stored_source_keys;
658 for (
auto const& domain_fragment : domain.fragments)
660 for (
auto const& codomain_fragment : codomain.fragments)
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);
671 for (std::size_t column = 0; column < domain_fragment.flattened_extent; ++column)
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)
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);
680 detail::block_element(block, block_indices);
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)});
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);
705 std::ranges::stable_sort(sector_order,
706 [&](std::size_t lhs, std::size_t rhs) {
return estimated_cost(lhs) > estimated_cost(rhs); });
709 measurements, batch_event, sector_order.size(),
710 [&](
auto& function) {
711 detail::execute_svd_sector_batch(block_execution_policy{}, sector_order.size(), function);
713 [&](std::size_t task) { factorize_sector(sector_order[task]); });
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());
720 return decomposition_type{tensor.symmetry(), tensor.domain(), tensor.codomain(), options, std::move(sectors)};
724template <ImmediateBlockTensorView Tensor,
class Measurements,
class Event>
730 return linalg::svd(matrix, sector_options);
732 return block_svd(tensor, options, measurements, batch_event, factorizer);
741template <ImmediateBlockTensorView Tensor>
745 performance::NoMeasurements measurements;
746 return block_svd(tensor, options, measurements,
nullptr);
749#if UNI20_BACKEND_CUSOLVER
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)
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>;
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()}};
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)
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>;
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};
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)
814 ERROR_IF(output.extent(0) != input.extent(1) || output.extent(1) != input.extent(0),
815 "CUDA transpose copy requires reversed matrix extents");
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);
823template <
class Scalar>
void set_cuda_identity(CudaMatrix<Scalar>& matrix)
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;
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);
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)
842 using scalar_type = block_tensor_value_t<Tensor>;
843 using source_key_type =
typename block_tensor_type_t<Tensor>::key_type;
845 CudaMatrix<scalar_type> matrix(resources,
static_cast<uni20::index_type>(codomain.flattened_extent),
848 for (
auto const& domain_fragment : domain.fragments)
850 for (
auto const& codomain_fragment : codomain.fragments)
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);
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);
879template <
class Scalar>
880[[nodiscard]]
auto cuda_svd_resident_result(cuda::DeviceResources& resources, CudaMatrix<Scalar>& matrix_work,
881 linalg::SvdOptions options)
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);
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)};
905 linalg::singular_value_decomposition(linalg::CusolverBackend{}, device_values, device_left, device_right,
906 matrix_work, options);
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);
922 ColumnMajorTensor<Scalar, 1>
values(uninitialized, rank);
924 std::vector<Scalar> host_values(rank);
925 for (std::size_t index = 0; index < rank; ++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)};
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,
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;
967 auto& resources = tensor.allocation_context();
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;
977 domain_sector_type domain;
978 codomain_sector_type codomain;
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())
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()))
991 if (options.right == linalg::SvdVectorExtent::Full)
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}});
1000 if (has_codomain && (!has_domain || codomain_sectors[codomain_sector].charge.raw_code() <
1001 domain_sectors[domain_sector].charge.raw_code()))
1003 if (options.left == linalg::SvdVectorExtent::Full)
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)});
1013 sector_inputs.push_back(
1014 {.domain = std::move(domain_sectors[domain_sector]), .codomain = std::move(codomain_sectors[codomain_sector])});
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)});
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;
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);
1051 [&](std::size_t task) { factorize_sector(sector_order[task]); });
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),
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 = {})
1069 performance::NoMeasurements measurements;
1070 return block_svd(tensor, options, measurements,
nullptr);
1075template <uni20::Real Real>
1079 auto [canonical, selected] = detail::canonical_svd_selection(spectrum, requested);
1080 auto statistics = detail::summarize_svd_selection(spectrum, std::span<unsigned char const>{selected});
1085template <uni20::Real Real>
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);
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);
1103template <uni20::Real Real>
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)
1113 if (!selected[ordinal]) complement.push_back(spectrum[ordinal].
id);
1121template <
class Decomposition>
1122using block_svd_factor_storage_t = tensor_storage_policy_t<typename Decomposition::matrix_type>;
1124template <
class Decomposition>
1125using block_svd_materialized_storage_t = PackedSparseBlockStorage<block_svd_factor_storage_t<Decomposition>>;
1127template <
class Decomposition>
using block_svd_materialized_diagonal_storage_t = PackedDiagonalBlockStorage<>;
1129template <
class Decomposition>
struct BlockSvdSelectionPlan
1131 BlockSpace bond_space;
1132 std::vector<std::vector<std::size_t>> sector_indices;
1133 std::vector<std::optional<std::size_t>> bond_coordinates;
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>
1140 std::vector<std::vector<std::size_t>> sector_indices(decomposition.sectors().size());
1141 for (BlockSvdStateId
const&
id : state_ids)
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);
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)
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()});
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)};
1170template <
class Decomposition>
1171auto make_left_singular_vectors(Decomposition
const& decomposition, BlockSvdSelectionPlan<Decomposition>
const& plan,
1172 bool absorb_singular_values =
false)
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)
1182 if (!plan.bond_coordinates[sector])
continue;
1183 for (
auto const& fragment : decomposition.sectors()[sector].codomain_fragments)
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);
1193 if constexpr (
requires {
1194 output_type(uninitialized, decomposition.symmetry(), Domain{plan.bond_space},
1195 decomposition.codomain(), keys, decomposition.allocation_context());
1197 return output_type(uninitialized, decomposition.symmetry(),
Domain{plan.bond_space}, decomposition.codomain(),
1198 std::move(keys), decomposition.allocation_context());
1200 return output_type(uninitialized, decomposition.symmetry(), Domain{plan.bond_space}, decomposition.codomain(),
1203 for (std::size_t sector = 0; sector < decomposition.sectors().size(); ++sector)
1205 if (!plan.bond_coordinates[sector])
continue;
1206 auto const& source = decomposition.sectors()[sector];
1207 for (
auto const& fragment : source.codomain_fragments)
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>)
1215 for (std::size_t selected_index = 0; selected_index < plan.sector_indices[sector].size(); ++selected_index)
1217 for (std::size_t row = 0;
row < fragment.flattened_extent; ++
row)
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);
1226 if (absorb_singular_values)
1227 value *= source.host_singular_values[plan.sector_indices[sector][selected_index]];
1228 block_element(block, output_indices) =
value;
1232#if UNI20_BACKEND_CUSOLVER
1235 static_assert(
decltype(block)::rank() == 2,
1236 "CUDA block-SVD materialization currently requires one codomain dense axis");
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)
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)
1253 linalg::scale<scalar_type>{source.host_singular_values[plan.sector_indices[sector][selected_index]]},
1268template <
class Decomposition>
1269auto make_right_singular_vectors_adjoint(Decomposition
const& decomposition,
1270 BlockSvdSelectionPlan<Decomposition>
const& plan,
1271 bool absorb_singular_values =
false)
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;
1279 std::vector<key_type> keys;
1280 for (std::size_t sector = 0; sector < decomposition.sectors().
size(); ++sector)
1282 if (!plan.bond_coordinates[sector])
continue;
1283 for (
auto const& fragment : decomposition.sectors()[sector].domain_fragments)
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);
1293 if constexpr (
requires {
1294 output_type(uninitialized, decomposition.symmetry(), decomposition.domain(),
1295 Codomain{plan.bond_space}, keys, decomposition.allocation_context());
1297 return output_type(uninitialized, decomposition.symmetry(), decomposition.domain(),
Codomain{plan.bond_space},
1298 std::move(keys), decomposition.allocation_context());
1300 return output_type(uninitialized, decomposition.symmetry(), decomposition.domain(), Codomain{plan.bond_space},
1303 for (std::size_t sector = 0; sector < decomposition.sectors().size(); ++sector)
1305 if (!plan.bond_coordinates[sector])
continue;
1306 auto const& source = decomposition.sectors()[sector];
1307 for (
auto const& fragment : source.domain_fragments)
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>)
1315 for (std::size_t selected_index = 0; selected_index < plan.sector_indices[sector].size(); ++selected_index)
1317 for (std::size_t column = 0; column < fragment.flattened_extent; ++column)
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);
1324 plan.sector_indices[sector][selected_index]),
1326 if (absorb_singular_values)
1327 value *= source.host_singular_values[plan.sector_indices[sector][selected_index]];
1328 block_element(block, output_indices) =
value;
1332#if UNI20_BACKEND_CUSOLVER
1335 static_assert(
decltype(block)::rank() == 2,
1336 "CUDA block-SVD materialization currently requires one domain dense axis");
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)
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)
1353 linalg::scale<scalar_type>{source.host_singular_values[plan.sector_indices[sector][selected_index]]},
1368template <
class Decomposition>
1369auto make_block_singular_values(Decomposition
const& decomposition, BlockSvdSelectionPlan<Decomposition>
const& plan)
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)
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]});
1383 output_type result(uninitialized, decomposition.symmetry(), Domain{plan.bond_space}, Codomain{plan.bond_space},
1385 for (std::size_t sector = 0; sector < decomposition.sectors().
size(); ++sector)
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)
1392 values[index] = decomposition.sectors()[sector].host_singular_values[plan.sector_indices[sector][index]];
1398template <
class Decomposition>
1400materialize_svd_with_absorption(Decomposition
const& decomposition,
1401 BlockSvdSelection<typename Decomposition::real_type>
const& selection,
1402 BlockSvdMaterializationOptions options,
bool absorb_left,
bool absorb_right)
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)};
1427template <
class Decomposition>
1432 return detail::materialize_svd_with_absorption(decomposition, selection, std::move(options),
false,
false);
1436template <
class Decomposition, detail::BlockSvdStateSelection Selection>
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);
1447template <
class Decomposition, detail::BlockSvdStateSelection Selection>
1449 Selection
const& selection,
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);
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.
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
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.
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.
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