Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
right_first_rabc.hpp
Go to the documentation of this file.
1
7#pragma once
8
21
22#include <algorithm>
23#include <array>
24#include <concepts>
25#include <cstddef>
26#include <numeric>
27#include <stdexcept>
28#include <tuple>
29#include <type_traits>
30#include <utility>
31#include <vector>
32
34{
35
43template <class IntermediateStorage, linalg::KernelBackendSelector ContractionSelector> struct RightFirstRabcBackend
44{
45 static constexpr std::string_view name = "right_first_rabc";
46
47 ContractionSelector contraction_selector;
48};
49
50namespace detail
51{
52
53template <class Tensor>
54concept ReadableRabcTensor = BlockTensorView<Tensor> && (block_tensor_type_t<Tensor>::dense_block_order() == 2);
55
56template <class Tensor>
58
59template <class Output, class Plan, class A, class B, class C>
60concept CompatibleRabcOperands =
61 WritableRabcTensor<Output> && ReadableRabcTensor<A> && ReadableRabcTensor<B> && ReadableRabcTensor<C> &&
62 RabcPlan<Plan> && std::same_as<typename std::remove_cvref_t<Plan>::r_key_type, block_tensor_key_t<Output>> &&
63 std::same_as<typename std::remove_cvref_t<Plan>::a_key_type, block_tensor_key_t<A>> &&
64 std::same_as<typename std::remove_cvref_t<Plan>::b_key_type, block_tensor_key_t<B>> &&
65 std::same_as<typename std::remove_cvref_t<Plan>::c_key_type, block_tensor_key_t<C>> &&
66 std::same_as<typename std::remove_cvref_t<Plan>::scalar_type, block_tensor_value_t<Output>> &&
67 std::same_as<block_tensor_value_t<Output>, block_tensor_value_t<A>> &&
68 std::same_as<block_tensor_value_t<Output>, block_tensor_value_t<B>> &&
69 std::same_as<block_tensor_value_t<Output>, block_tensor_value_t<C>>;
70
71template <class Tensor>
72using mutable_rabc_mdspec_t = decltype(mdspec_of(std::declval<block_tensor_mutable_block_t<Tensor>&>()));
73
74template <class Tensor>
75using const_rabc_mdspec_t = decltype(mdspec_of(std::declval<block_tensor_const_block_t<Tensor> const&>()));
76
77template <class IntermediateStorage, class ContractionSelector, class Output, class Scalar, class A, class B, class C>
78consteval bool right_first_types_compatible()
79{
81 using intermediate_mutable_mdspec = decltype(mdspec_of(std::declval<intermediate_type&>()));
82 using intermediate_const_mdspec = decltype(mdspec_of(std::declval<intermediate_type const&>()));
83 using output_mdspec = mutable_rabc_mdspec_t<Output>;
84 using a_mdspec = const_rabc_mdspec_t<A>;
85 using b_mdspec = const_rabc_mdspec_t<B>;
86 using c_mdspec = const_rabc_mdspec_t<C>;
87
88 constexpr bool right_product_is_total =
89 linalg::probe_dispatch_kernel_types<ContractionSelector const&, linalg::contract_op<2, 2, 1>,
90 intermediate_mutable_mdspec&, Scalar&, b_mdspec&, c_mdspec&, Scalar&>() ==
92 constexpr bool accumulation_is_total =
93 linalg::probe_dispatch_kernel_types<ContractionSelector const&, linalg::contract_op<2, 2, 1>, output_mdspec&,
94 Scalar&, a_mdspec&, intermediate_const_mdspec&, Scalar&>() ==
96 return right_product_is_total && accumulation_is_total;
97}
98
99template <class IntermediateStorage, uni20::Scalar Scalar, TensorView Prototype>
100[[nodiscard]] auto make_rabc_intermediate(Prototype const& prototype, index_type rows,
102{
104 if constexpr (requires {
105 mdspec_of(prototype).data_descriptor().buffer().resources();
106 intermediate_type(mdspec_of(prototype).data_descriptor().buffer().resources(), rows, columns);
107 })
108 {
109 auto descriptor = mdspec_of(prototype);
110 return intermediate_type(descriptor.data_descriptor().buffer().resources(), rows, columns);
111 }
112 else
113 {
114 return intermediate_type(rows, columns);
115 }
116}
117
118template <class Function> void execute_rabc_batch(SerialBlockExecution, std::size_t size, Function&& function)
119{
120 for (std::size_t index = 0; index < size; ++index)
121 function(index);
122}
123
124template <class Function> void execute_rabc_batch(SchedulerBatchBlockExecution, std::size_t size, Function&& function)
125{
126 async::execute_batch(size, std::forward<Function>(function));
127}
128
129template <uni20::RealOrComplex Scalar> struct BoundRabcTerm
130{
131 std::size_t r_ordinal;
132 std::size_t a_ordinal;
133 std::size_t b_ordinal;
134 std::size_t c_ordinal;
135 Scalar coefficient;
136};
137
138template <class Tensor, class Key>
139[[nodiscard]] auto bind_rabc_keys(Tensor const& tensor, std::span<Key const> keys) -> std::vector<std::size_t>
140{
141 std::vector<std::size_t> result;
142 result.reserve(keys.size());
143 for (auto const& key : keys)
144 {
145 auto const found = std::ranges::lower_bound(tensor.stored_keys(), key);
146 if (found == tensor.stored_keys().end() || *found != key)
147 throw std::invalid_argument("R/A/B/C logical key is not stored by its operand");
148 result.push_back(static_cast<std::size_t>(found - tensor.stored_keys().begin()));
149 }
150 return result;
151}
152
153template <class Output, class Plan, class A, class B, class C>
154 requires CompatibleRabcOperands<Output, Plan, A, B, C>
155[[nodiscard]] auto bind_rabc_operands(Output const& output, Plan const& plan, A const& a, B const& b, C const& c)
156 -> std::vector<BoundRabcTerm<typename std::remove_cvref_t<Plan>::scalar_type>>
157{
158 using scalar_type = typename std::remove_cvref_t<Plan>::scalar_type;
159 auto const r_ordinals = bind_rabc_keys(output, plan.r_keys());
160 auto const a_ordinals = bind_rabc_keys(a, plan.a_keys());
161 auto const b_ordinals = bind_rabc_keys(b, plan.b_keys());
162 auto const c_ordinals = bind_rabc_keys(c, plan.c_keys());
163
164 std::vector<BoundRabcTerm<scalar_type>> result;
165 result.reserve(plan.term_count());
166 for (auto const& term : plan.terms())
167 {
168 BoundRabcTerm<scalar_type> binding{.r_ordinal = r_ordinals[term.r_key_index],
169 .a_ordinal = a_ordinals[term.a_key_index],
170 .b_ordinal = b_ordinals[term.b_key_index],
171 .c_ordinal = c_ordinals[term.c_key_index],
172 .coefficient = term.coefficient};
173
174 auto const output_block = output.block_by_ordinal(binding.r_ordinal);
175 auto const a_block = a.block_by_ordinal(binding.a_ordinal);
176 auto const b_block = b.block_by_ordinal(binding.b_ordinal);
177 auto const c_block = c.block_by_ordinal(binding.c_ordinal);
178 if (a_block.extent(1) != b_block.extent(0) || b_block.extent(1) != c_block.extent(1) ||
179 output_block.extent(0) != a_block.extent(0) || output_block.extent(1) != c_block.extent(0))
180 throw std::invalid_argument("R/A/B/C contraction term has incompatible dense block extents");
181 result.push_back(std::move(binding));
182 }
183
184 std::ranges::stable_sort(result, [](auto const& lhs, auto const& rhs) {
185 return std::tuple{lhs.r_ordinal, lhs.a_ordinal, lhs.b_ordinal, lhs.c_ordinal} <
186 std::tuple{rhs.r_ordinal, rhs.a_ordinal, rhs.b_ordinal, rhs.c_ordinal};
187 });
188 return result;
189}
190
191struct RightFirstRabcGroup
192{
193 std::size_t b_ordinal;
194 std::size_t c_ordinal;
195};
196
197template <class Terms>
198[[nodiscard]] auto make_right_first_groups(Terms const& terms,
199 std::vector<std::size_t>& term_group) -> std::vector<RightFirstRabcGroup>
200{
201 std::vector<std::size_t> order(terms.size());
202 std::iota(order.begin(), order.end(), std::size_t{});
203 std::ranges::stable_sort(order, [&](std::size_t lhs, std::size_t rhs) {
204 return std::tuple{terms[lhs].b_ordinal, terms[lhs].c_ordinal} <
205 std::tuple{terms[rhs].b_ordinal, terms[rhs].c_ordinal};
206 });
207
208 std::vector<RightFirstRabcGroup> groups;
209 groups.reserve(order.size());
210 for (std::size_t const term_index : order)
211 {
212 auto const& term = terms[term_index];
213 if (groups.empty() || groups.back().b_ordinal != term.b_ordinal || groups.back().c_ordinal != term.c_ordinal)
214 groups.push_back({.b_ordinal = term.b_ordinal, .c_ordinal = term.c_ordinal});
215 term_group[term_index] = groups.size() - 1;
216 }
217 return groups;
218}
219
220} // namespace detail
221
233template <class IntermediateStorage, RabcPlan Plan, linalg::KernelBackendSelector ContractionSelector>
235 public:
236 using plan_type = std::remove_cvref_t<Plan>;
237 using scalar_type = typename plan_type::scalar_type;
238 using contraction_selector_type = ContractionSelector;
239
241
249 template <detail::WritableRabcTensor Output, detail::ReadableRabcTensor A, detail::ReadableRabcTensor B,
250 detail::ReadableRabcTensor C>
251 requires detail::CompatibleRabcOperands<Output, plan_type, A, B, C>
253 A const& a, B const& b, C const& c)
254 : selector_(std::move(selector)), plan_(std::move(plan)), term_group_(plan_.term_count())
255 {
256 bound_terms_ = detail::bind_rabc_operands(output, plan_, a, b, c);
257 groups_ = detail::make_right_first_groups(bound_terms_, term_group_);
258
259 intermediates_.reserve(groups_.size());
260 for (auto const& group : groups_)
261 {
262 auto const b_block = b.block_by_ordinal(group.b_ordinal);
263 auto const c_block = c.block_by_ordinal(group.c_ordinal);
264 intermediates_.push_back(detail::make_rabc_intermediate<IntermediateStorage, scalar_type>(
265 b_block, b_block.extent(0), c_block.extent(0)));
266 }
267
268 output_offsets_.assign(output.stored_block_count() + 1, 0);
269 for (auto const& term : bound_terms_)
270 ++output_offsets_[term.r_ordinal + 1];
271 std::partial_sum(output_offsets_.begin(), output_offsets_.end(), output_offsets_.begin());
272
273 output_order_.resize(output.stored_block_count());
274 std::iota(output_order_.begin(), output_order_.end(), std::size_t{});
275 std::vector<long double> output_cost(output.stored_block_count(), 0);
276 for (std::size_t index = 0; index < bound_terms_.size(); ++index)
277 {
278 auto const& term = bound_terms_[index];
279 auto const a_block = a.block_by_ordinal(term.a_ordinal);
280 auto const& intermediate = intermediates_[term_group_[index]];
281 output_cost[term.r_ordinal] +=
282 static_cast<long double>(a_block.extent(0)) * a_block.extent(1) * intermediate.extent(1);
283 }
284 std::ranges::stable_sort(output_order_,
285 [&](std::size_t lhs, std::size_t rhs) { return output_cost[lhs] > output_cost[rhs]; });
286 }
287
296 template <detail::WritableRabcTensor Output, detail::ReadableRabcTensor A, detail::ReadableRabcTensor B,
297 detail::ReadableRabcTensor C>
298 requires detail::CompatibleRabcOperands<Output, plan_type, A, B, C>
299 void operator()(Output& output, A const& a, B const& b, C const& c)
300 {
302 constexpr std::array<std::pair<std::size_t, std::size_t>, 1> right_dimensions{std::pair{1U, 1U}};
303 detail::execute_rabc_batch(execution_policy{}, groups_.size(), [&](std::size_t group_index) {
304 auto const& group = groups_[group_index];
305 auto const b_block = b.block_by_ordinal(group.b_ordinal);
306 auto const c_block = c.block_by_ordinal(group.c_ordinal);
307 linalg::contract(selector_, intermediates_[group_index], scalar_type{1}, b_block, c_block, right_dimensions,
308 scalar_type{});
309 });
310
311 constexpr std::array<std::pair<std::size_t, std::size_t>, 1> left_dimensions{std::pair{1U, 0U}};
312 detail::execute_rabc_batch(execution_policy{}, output_order_.size(), [&](std::size_t ordered_index) {
313 std::size_t const output_ordinal = output_order_[ordered_index];
314 auto output_block = output.block_by_ordinal(output_ordinal);
315 std::size_t const first = output_offsets_[output_ordinal];
316 std::size_t const last = output_offsets_[output_ordinal + 1];
317 if (first == last)
318 {
319 uni20::fill(output_block, scalar_type{});
320 return;
321 }
322
323 for (std::size_t index = first; index < last; ++index)
324 {
325 auto const& term = bound_terms_[index];
326 auto const a_block = a.block_by_ordinal(term.a_ordinal);
327 scalar_type const beta = index == first ? scalar_type{} : scalar_type{1};
328 linalg::contract(selector_, output_block, term.coefficient, a_block, intermediates_[term_group_[index]],
329 left_dimensions, beta);
330 }
331 });
332 }
333
335 [[nodiscard]] auto intermediate_count() const noexcept -> std::size_t { return intermediates_.size(); }
336
338 [[nodiscard]] auto plan() const noexcept -> plan_type const& { return plan_; }
339
340 private:
342 plan_type plan_;
343 std::vector<detail::BoundRabcTerm<scalar_type>> bound_terms_;
344 std::vector<std::size_t> term_group_;
345 std::vector<detail::RightFirstRabcGroup> groups_;
346 std::vector<intermediate_type> intermediates_;
347 std::vector<std::size_t> output_offsets_;
348 std::vector<std::size_t> output_order_;
349};
350
352template <class IntermediateStorage, class ContractionSelector, detail::WritableRabcTensor Output, RabcPlan Plan,
353 detail::ReadableRabcTensor A, detail::ReadableRabcTensor B, detail::ReadableRabcTensor C>
354 requires detail::CompatibleRabcOperands<Output, Plan, A, B, C>
356 rabc_contract_op const&, Output&, Plan const&, A const&, B const&, C const&)
357{
358 using scalar_type = typename std::remove_cvref_t<Plan>::scalar_type;
359 if constexpr (detail::right_first_types_compatible<IntermediateStorage, ContractionSelector, Output, scalar_type, A,
360 B, C>())
362 else
364}
365
367template <class IntermediateStorage, class ContractionSelector, detail::WritableRabcTensor Output, RabcPlan Plan,
368 detail::ReadableRabcTensor A, detail::ReadableRabcTensor B, detail::ReadableRabcTensor C>
369 requires detail::CompatibleRabcOperands<Output, Plan, A, B, C> &&
370 (detail::right_first_types_compatible<IntermediateStorage, ContractionSelector, Output,
371 typename std::remove_cvref_t<Plan>::scalar_type, A, B, C>())
372auto try_kernel(RightFirstRabcBackend<IntermediateStorage, ContractionSelector> const& backend, rabc_contract_op const&,
373 Output& output, Plan const& plan, A const& a, B const& b, C const& c) -> linalg::KernelAttempt
374{
376 backend.contraction_selector, output, plan, a, b, c);
377 prepared(output, a, b, c);
379}
380
381} // namespace uni20::tensor_network
RAII leases that resolve device descriptors and tensor views to mdspans.
Ordered backend selector values shared by tensor storage and linalg dispatch.
Concepts and associated types for BlockTensor-level views.
Defines local sparse and packed-complete BlockTensor storage policies.
General-purpose owning tensor with immediate or deferred mdspan metadata.
Definition basic_tensor.hpp:109
Prepared right-first executor for one fixed R/A/B/C structure.
Definition right_first_rabc.hpp:234
typename plan_type::scalar_type scalar_type
Definition right_first_rabc.hpp:237
std::remove_cvref_t< Plan > plan_type
Definition right_first_rabc.hpp:236
ContractionSelector contraction_selector_type
Definition right_first_rabc.hpp:238
void operator()(Output &output, A const &a, B const &b, C const &c)
Execute using the retained schedule and workspace.
Definition right_first_rabc.hpp:299
PreparedRightFirstRabcContraction(contraction_selector_type selector, Output const &output, plan_type plan, A const &a, B const &b, C const &c)
Compile a fixed execution schedule and allocate its workspace.
Definition right_first_rabc.hpp:252
auto plan() const noexcept -> plan_type const &
Return the immutable execution-neutral coefficient plan.
Definition right_first_rabc.hpp:338
auto intermediate_count() const noexcept -> std::size_t
Return the number of retained (B,C) intermediate blocks.
Definition right_first_rabc.hpp:335
Tensor-level view of a symmetry-preserving collection of dense blocks.
Definition block_tensor_concepts.hpp:52
BlockTensorView whose stored dense blocks support eventual mutation.
Definition block_tensor_concepts.hpp:73
Concept for numeric scalar types (integer, real, or complex).
Definition scalar_concepts.hpp:81
Concrete logical R/A/B/C coefficient-plan type.
Definition rabc_contraction_plan.hpp:156
Coroutine scheduler for AsyncTask.
Operation-tag backend dispatch over tensor-view operands.
Named backend-neutral function objects for elementwise transforms.
Fixed-output pairwise Tensor contraction front end.
Definition qnum.hpp:403
std::pair< int, int > size(std::FILE *stream)
Return the terminal size associated with a C stream.
Definition terminal.cpp:504
void execute_batch(std::size_t size, Function &&function)
Execute a synchronous lightweight batch on the global scheduler.
Definition debug_scheduler.hpp:245
Definition cli.cpp:6
void contract(BackendSelector selector, async::Async< OutputTensor > &output, Alpha &&alpha, async::Async< LhsTensor > const &lhs, async::Async< RhsTensor > const &rhs, ContractionAxes< LhsRank, RhsRank, ContractedRank > axes, Beta &&beta)
Schedule a fixed-output pairwise contraction through an explicit selector.
Definition linalg/async/contract.hpp:84
constexpr KernelAcceptance< KernelTypeAcceptance::no > kernel_types_no
Report that a backend rejects the argument types.
Definition dispatch_error.hpp:34
constexpr KernelAcceptance< KernelTypeAcceptance::yes > kernel_types_yes
Report that a backend accepts every instance of the argument types.
Definition dispatch_error.hpp:40
Definition right_first_rabc.hpp:34
auto try_kernel(RightFirstRabcBackend< IntermediateStorage, ContractionSelector > const &backend, rabc_contract_op const &, Output &output, Plan const &plan, A const &a, B const &b, C const &c) -> linalg::KernelAttempt
Execute a storage-domain right-first R/A/B/C contraction.
Definition right_first_rabc.hpp:372
consteval auto kernel_accepts_types(RightFirstRabcBackend< IntermediateStorage, ContractionSelector > const &, rabc_contract_op const &, Output &, Plan const &, A const &, B const &, C const &)
Report right-first eligibility for matrix-block operands.
Definition right_first_rabc.hpp:355
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
decltype(std::declval< std::remove_reference_t< Tensor > & >().block_by_ordinal(std::size_t{})) block_tensor_mutable_block_t
Writable dense-block TensorView exposed by stable block ordinal.
Definition block_tensor_concepts.hpp:44
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
typename block_tensor_type_t< Tensor >::key_type block_tensor_key_t
Logical block-key type exposed by a BlockTensor-level object.
Definition block_tensor_concepts.hpp:28
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
std::ptrdiff_t index_type
Definition types.hpp:19
mpreal beta(mpreal const &a, mpreal const &x)
Real Beta function; positive rational/integer pairs retain exact values.
Definition mpreal_special.hpp:164
decltype(std::declval< block_tensor_type_t< Tensor > const & >().block_by_ordinal(std::size_t{})) block_tensor_const_block_t
Read-only dense-block TensorView exposed by stable block ordinal.
Definition block_tensor_concepts.hpp:39
Sparse logical plans for R/A/B/C block contractions.
Operation tag for sparse R/A/B/C contraction dispatch.
Trait to extract the scalar type from a type T.
Definition scalar_traits.hpp:93
Executor that reuses right-first (B_b,C_c) intermediates.
Definition right_first_rabc.hpp:44
static constexpr std::string_view name
Definition right_first_rabc.hpp:45
ContractionSelector contraction_selector
Definition right_first_rabc.hpp:47
Fixed-output sparse R/A/B/C contraction operation tag.
Definition rabc_operation.hpp:18
Backend-dispatched elementwise overwrite and update operations.
General-purpose owning tensors with runtime extents.