Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
block_tensor_linear.hpp
Go to the documentation of this file.
1
7#pragma once
8
10#include <uni20/config.hpp>
11#include <uni20/core/math.hpp>
21
22#if UNI20_BACKEND_CUDA
24#endif
25
26#include <algorithm>
27#include <array>
28#include <cmath>
29#include <concepts>
30#include <cstddef>
31#include <memory>
32#include <optional>
33#include <span>
34#include <stdexcept>
35#include <type_traits>
36#include <utility>
37#include <vector>
38
39namespace uni20
40{
41namespace detail
42{
43
44template <class Lhs, class Rhs>
45concept CompatibleBlockTensorValues =
46 BlockTensorView<Lhs> && BlockTensorView<Rhs> &&
47 std::same_as<typename block_tensor_type_t<Lhs>::value_type, typename block_tensor_type_t<Rhs>::value_type> &&
48 std::same_as<typename block_tensor_type_t<Lhs>::key_type, typename block_tensor_type_t<Rhs>::key_type> &&
49 std::same_as<typename block_tensor_type_t<Lhs>::domain_type, typename block_tensor_type_t<Rhs>::domain_type> &&
50 std::same_as<typename block_tensor_type_t<Lhs>::codomain_type, typename block_tensor_type_t<Rhs>::codomain_type>;
51
52template <class Output, class... Inputs>
53concept CompatibleLinearOutputRepresentation =
54 BlockTensorView<Output> && (BlockTensorView<Inputs> && ...) &&
55 (!DiagonalBlockTensorView<Output> || (DiagonalBlockTensorView<Inputs> && ...));
56
57template <class OutputStorage, class FirstInput, class... OtherInputs>
58concept CompatibleLinearOutputStorage =
59 BlockTensorStorage<OutputStorage> && BlockTensorView<FirstInput> && (BlockTensorView<OtherInputs> && ...) &&
60 (!DiagonalBlockStorage<OutputStorage> ||
61 (DiagonalBlockTensorView<FirstInput> && (DiagonalBlockTensorView<OtherInputs> && ...))) &&
62 ((ImmediateBlockTensorView<FirstInput> && (ImmediateBlockTensorView<OtherInputs> && ...) &&
63 ImmediateLocalBlockStorageFor<OutputStorage, block_tensor_value_t<FirstInput>,
64 block_tensor_type_t<FirstInput>::key_coordinate_count(),
65 block_tensor_type_t<FirstInput>::dense_block_order()>) ||
66 (AsyncBlockTensorView<FirstInput> && (AsyncBlockTensorView<OtherInputs> && ...) &&
67 AsyncLocalBlockStorageFor<OutputStorage, block_tensor_value_t<FirstInput>,
68 block_tensor_type_t<FirstInput>::key_coordinate_count(),
69 block_tensor_type_t<FirstInput>::dense_block_order()>));
70
71template <class Lhs, class Rhs> void require_compatible_block_tensor_values(Lhs const& lhs, Rhs const& rhs)
72{
73 if (lhs.symmetry() != rhs.symmetry())
74 {
75 throw std::invalid_argument("BlockTensor linear operation requires matching symmetries");
76 }
77 if (lhs.domain() != rhs.domain() || lhs.codomain() != rhs.codomain())
78 {
79 throw std::invalid_argument("BlockTensor linear operation requires exactly equal boundaries");
80 }
81}
82
83template <class Superset, class Subset> void require_stored_key_superset(Superset const& superset, Subset const& subset)
84{
85 if (!std::ranges::includes(superset.stored_keys(), subset.stored_keys()))
86 {
87 throw std::invalid_argument("fixed BlockTensor output does not store every required input block");
88 }
89}
90
91template <BlockTensorView Tensor>
92auto find_stored_key_ordinal(Tensor const& tensor, block_tensor_key_t<Tensor> const& key) -> std::optional<std::size_t>
93{
94 auto const found = std::ranges::lower_bound(tensor.stored_keys(), key);
95 if (found == tensor.stored_keys().end() || *found != key) return std::nullopt;
96 return static_cast<std::size_t>(found - tensor.stored_keys().begin());
97}
98
99template <class Lhs, class Rhs>
100auto stored_key_union(Lhs const& lhs, Rhs const& rhs) -> std::vector<typename block_tensor_type_t<Lhs>::key_type>
101{
102 using key_type = typename block_tensor_type_t<Lhs>::key_type;
103 std::vector<key_type> result;
104 result.reserve(lhs.stored_block_count() + rhs.stored_block_count());
105 std::ranges::set_union(lhs.stored_keys(), rhs.stored_keys(), std::back_inserter(result));
106 return result;
107}
108
109struct BlockIntersectionBinding
110{
111 std::size_t lhs_ordinal;
112 std::size_t rhs_ordinal;
113};
114
115template <class Lhs, class Rhs>
116auto stored_key_intersection_bindings(Lhs const& lhs, Rhs const& rhs) -> std::vector<BlockIntersectionBinding>
117{
118 std::vector<BlockIntersectionBinding> result;
119 result.reserve(std::min(lhs.stored_block_count(), rhs.stored_block_count()));
120 auto const lhs_keys = lhs.stored_keys();
121 auto const rhs_keys = rhs.stored_keys();
122 std::size_t lhs_ordinal = 0;
123 std::size_t rhs_ordinal = 0;
124 while (lhs_ordinal < lhs_keys.size() && rhs_ordinal < rhs_keys.size())
125 {
126 if (lhs_keys[lhs_ordinal] < rhs_keys[rhs_ordinal])
127 {
128 ++lhs_ordinal;
129 }
130 else if (rhs_keys[rhs_ordinal] < lhs_keys[lhs_ordinal])
131 {
132 ++rhs_ordinal;
133 }
134 else
135 {
136 result.push_back({lhs_ordinal++, rhs_ordinal++});
137 }
138 }
139 return result;
140}
141
142template <class Function> void execute_linear_block_batch(SerialBlockExecution, std::size_t size, Function&& function)
143{
144 for (std::size_t index = 0; index < size; ++index)
145 function(index);
146}
147
148template <class Function>
149void execute_linear_block_batch(SchedulerBatchBlockExecution, std::size_t size, Function&& function)
150{
151 async::execute_batch(size, std::forward<Function>(function));
152}
153
154template <class Tensor>
155using block_execution_policy_t = typename block_tensor_type_t<Tensor>::storage_policy::block_execution_policy;
156
157template <class Lhs, class Rhs>
158using reduction_execution_policy_t =
159 std::conditional_t<std::same_as<block_execution_policy_t<Lhs>, SchedulerBatchBlockExecution> ||
160 std::same_as<block_execution_policy_t<Rhs>, SchedulerBatchBlockExecution>,
161 SchedulerBatchBlockExecution, SerialBlockExecution>;
162
163template <class Lhs, class Rhs> constexpr auto same_block_tensor_object(Lhs const& lhs, Rhs const& rhs) noexcept -> bool
164{
165 if constexpr (std::same_as<block_tensor_type_t<Lhs>, block_tensor_type_t<Rhs>>)
166 {
167 return std::addressof(lhs) == std::addressof(rhs);
168 }
169 else
170 {
171 return false;
172 }
173}
174
175#if UNI20_BACKEND_CUDA
176
177template <class Tensor>
178concept CudaPartitionedBlockTensor = BlockTensorView<Tensor> && requires(Tensor& tensor, Tensor const& const_tensor) {
179 { tensor.storage().buffer() } -> std::same_as<cuda::PartitionedCudaBuffer<block_tensor_value_t<Tensor>>&>;
180 { const_tensor.storage().buffer() } -> std::same_as<cuda::PartitionedCudaBuffer<block_tensor_value_t<Tensor>> const&>;
181 { const_tensor.storage().offsets() } -> std::same_as<std::span<std::size_t const>>;
182};
183
184template <CudaPartitionedBlockTensor Lhs, CudaPartitionedBlockTensor Rhs>
185[[nodiscard]] bool same_packed_layout(Lhs const& lhs, Rhs const& rhs)
186{
187 return std::ranges::equal(lhs.stored_keys(), rhs.stored_keys()) &&
188 std::ranges::equal(lhs.storage().offsets(), rhs.storage().offsets()) &&
189 lhs.storage().buffer().size() == rhs.storage().buffer().size();
190}
191
192#endif
193
194template <class Block> void set_zero_block(Block&& block)
195{
196 using value_type = tensor_element_t<Block>;
197 auto span = mdspec_of(block);
198 if constexpr (MutableDiagonalMdspecLike<decltype(span)>)
199 uni20::fill(block.backend_selector(), diagonal_components(span), value_type{});
200 else
201 uni20::fill(std::forward<Block>(block), value_type{});
202}
203
204template <class Block, class Scalar> void scale_block(Block&& block, Scalar const& factor)
205{
206 auto span = mdspec_of(block);
207 if constexpr (MutableDiagonalMdspecLike<decltype(span)>)
208 uni20::transform_inplace(block.backend_selector(), diagonal_components(span), linalg::scale{factor});
209 else
210 uni20::transform_inplace(std::forward<Block>(block), linalg::scale{factor});
211}
212
213template <StridedMdspecLike Span> [[nodiscard]] auto dense_diagonal_components(Span const& span)
214{
215 using span_type = std::remove_cvref_t<Span>;
216 using element_type = typename span_type::element_type;
217 using index_type = typename span_type::index_type;
218 using extents_type = stdex::dextents<index_type, 1>;
219 using layout_type = stdex::layout_stride;
220 using mapping_type = typename layout_type::template mapping<extents_type>;
221 using accessor_type = typename span_type::accessor_type;
222
223 index_type extent = index_type{1};
224 index_type stride = index_type{1};
225 if constexpr (span_type::rank() > 0)
226 {
227 extent = span.extent(0);
228 stride = span.stride(0);
229 for (std::size_t axis = 1; axis < span_type::rank(); ++axis)
230 {
231 extent = std::min(extent, span.extent(axis));
232 stride += span.stride(axis);
233 }
234 }
235 auto mapping = mapping_type{extents_type{extent}, std::array<index_type, 1>{stride}};
236 if constexpr (MdspanLike<span_type>)
237 {
238 using result_type = stdex::mdspan<element_type, extents_type, layout_type, accessor_type>;
239 return result_type{span.data_handle(), std::move(mapping), span.accessor()};
240 }
241 else
242 {
243 using result_type =
244 mdspec<element_type, extents_type, layout_type, accessor_type, typename span_type::data_descriptor_type>;
245 return result_type{span.data_descriptor(), std::move(mapping), span.accessor()};
246 }
247}
248
249template <class LhsBlock, class RhsBlock> [[nodiscard]] auto inner_product_block_host(LhsBlock&& lhs, RhsBlock&& rhs)
250{
251 auto lhs_span = mdspec_of(lhs);
252 auto rhs_span = mdspec_of(rhs);
253 if constexpr (DiagonalMdspecLike<decltype(lhs_span)> && DiagonalMdspecLike<decltype(rhs_span)>)
254 {
255 return uni20::inner_product_host(lhs.backend_selector(), diagonal_components(lhs_span),
256 diagonal_components(rhs_span));
257 }
258 else if constexpr (DiagonalMdspecLike<decltype(lhs_span)> && StridedMdspecLike<decltype(rhs_span)>)
259 {
260 return uni20::inner_product_host(lhs.backend_selector(), diagonal_components(lhs_span),
261 dense_diagonal_components(rhs_span));
262 }
263 else if constexpr (StridedMdspecLike<decltype(lhs_span)> && DiagonalMdspecLike<decltype(rhs_span)>)
264 {
265 return uni20::inner_product_host(lhs.backend_selector(), dense_diagonal_components(lhs_span),
266 diagonal_components(rhs_span));
267 }
268 else
269 {
270 return uni20::inner_product_host(std::forward<LhsBlock>(lhs), std::forward<RhsBlock>(rhs));
271 }
272}
273
274template <class OutputBlock, class InputBlock, class Scalar>
275void assign_scale_block(OutputBlock&& output, Scalar const& factor, InputBlock&& input)
276{
277 auto output_span = mdspec_of(output);
278 auto input_span = mdspec_of(input);
279 if constexpr (MutableDiagonalMdspecLike<decltype(output_span)> && DiagonalMdspecLike<decltype(input_span)>)
280 uni20::assign_transform(output.backend_selector(), diagonal_components(output_span), linalg::scale{factor},
281 diagonal_components(input_span));
282 else if constexpr (MutableStridedMdspecLike<decltype(output_span)> && DiagonalMdspecLike<decltype(input_span)> &&
283 requires { uni20::isfinite(factor); })
284 {
285 if (!uni20::isfinite(factor))
286 {
287 set_zero_block(output);
288 uni20::assign_transform(output.backend_selector(), dense_diagonal_components(output_span), linalg::scale{factor},
289 diagonal_components(input_span));
290 return;
291 }
292 uni20::assign_transform(std::forward<OutputBlock>(output), linalg::scale{factor}, std::forward<InputBlock>(input));
293 }
294 else
295 uni20::assign_transform(std::forward<OutputBlock>(output), linalg::scale{factor}, std::forward<InputBlock>(input));
296}
297
298template <class Block> [[nodiscard]] auto norm_block_host(Block&& block)
299{
300 auto span = mdspec_of(block);
301 if constexpr (DiagonalMdspecLike<decltype(span)>)
302 return uni20::norm_host(block.backend_selector(), diagonal_components(span));
303 else
304 return uni20::norm_host(std::forward<Block>(block));
305}
306
307template <class OutputBlock, class LhsBlock, class RhsBlock>
308void add_block(OutputBlock&& output, LhsBlock&& lhs, RhsBlock&& rhs)
309{
310 auto output_span = mdspec_of(output);
311 auto lhs_span = mdspec_of(lhs);
312 auto rhs_span = mdspec_of(rhs);
313 if constexpr (MutableDiagonalMdspecLike<decltype(output_span)> && DiagonalMdspecLike<decltype(lhs_span)> &&
314 DiagonalMdspecLike<decltype(rhs_span)>)
315 uni20::assign_transform(output.backend_selector(), diagonal_components(output_span), linalg::add{},
316 diagonal_components(lhs_span), diagonal_components(rhs_span));
317 else
318 uni20::assign_transform(std::forward<OutputBlock>(output), linalg::add{}, std::forward<LhsBlock>(lhs),
319 std::forward<RhsBlock>(rhs));
320}
321
322template <class OutputBlock, class InputBlock> void add_inplace_block(OutputBlock&& output, InputBlock&& input)
323{
324 auto output_span = mdspec_of(output);
325 auto input_span = mdspec_of(input);
326 if constexpr (MutableDiagonalMdspecLike<decltype(output_span)> && DiagonalMdspecLike<decltype(input_span)>)
327 uni20::transform_inplace(output.backend_selector(), diagonal_components(output_span), linalg::add{},
328 diagonal_components(input_span));
329 else
330 uni20::transform_inplace(std::forward<OutputBlock>(output), linalg::add{}, std::forward<InputBlock>(input));
331}
332
333template <class OutputBlock, class Scalar, class InputBlock>
334void axpy_block(OutputBlock&& output, Scalar const& factor, InputBlock&& input)
335{
336 auto output_span = mdspec_of(output);
337 auto input_span = mdspec_of(input);
338 if constexpr (MutableDiagonalMdspecLike<decltype(output_span)> && DiagonalMdspecLike<decltype(input_span)>)
339 uni20::transform_inplace(output.backend_selector(), diagonal_components(output_span), linalg::add_scaled{factor},
340 diagonal_components(input_span));
341 else if constexpr (MutableStridedMdspecLike<decltype(output_span)> && DiagonalMdspecLike<decltype(input_span)> &&
342 requires { uni20::isfinite(factor); })
343 {
344 if (!uni20::isfinite(factor))
345 {
346 uni20::transform_inplace(output.backend_selector(), dense_diagonal_components(output_span),
347 linalg::add_scaled{factor}, diagonal_components(input_span));
348 return;
349 }
350 uni20::transform_inplace(std::forward<OutputBlock>(output), linalg::add_scaled{factor},
351 std::forward<InputBlock>(input));
352 }
353 else
354 uni20::transform_inplace(std::forward<OutputBlock>(output), linalg::add_scaled{factor},
355 std::forward<InputBlock>(input));
356}
357
358template <class RequestedStorage, class Tensor>
359using selected_linear_storage_t =
360 std::conditional_t<std::is_void_v<RequestedStorage>, typename block_tensor_type_t<Tensor>::storage_policy,
361 RequestedStorage>;
362
363} // namespace detail
364
369template <MutableBlockTensorView Tensor> void set_zero(Tensor& tensor)
370{
371#if UNI20_BACKEND_CUDA
372 if constexpr (detail::CudaPartitionedBlockTensor<Tensor> &&
373 linalg::cuda_partitioned_fill_scalar<block_tensor_value_t<Tensor>>)
374 {
376 return;
377 }
378#endif
380 {
381 detail::execute_linear_block_batch(detail::block_execution_policy_t<Tensor>{}, tensor.stored_block_count(),
382 [&](std::size_t ordinal) {
383 auto block = tensor.block_by_ordinal(ordinal);
384 detail::set_zero_block(block);
385 });
386 }
387 else
388 {
389 for (std::size_t ordinal = 0; ordinal < tensor.stored_block_count(); ++ordinal)
390 {
391 auto& block = tensor.async_block_by_ordinal(ordinal);
392 using value_type = block_tensor_value_t<Tensor>;
393 uni20::fill(block, value_type{});
394 }
395 }
396}
397
400template <MutableBlockTensorView Tensor, class Scalar> void scale(Tensor& tensor, Scalar factor)
401{
402#if UNI20_BACKEND_CUDA
403 if constexpr (detail::CudaPartitionedBlockTensor<Tensor> &&
404 linalg::cuda_partitioned_scale_scalars<block_tensor_value_t<Tensor>, Scalar>)
405 {
406 if (!tensor.storage().has_padding() || uni20::isfinite(factor))
407 {
408 linalg::cuda_partitioned_scale(tensor.storage().buffer(), factor);
409 return;
410 }
411 }
412#endif
414 {
415 detail::execute_linear_block_batch(detail::block_execution_policy_t<Tensor>{}, tensor.stored_block_count(),
416 [&](std::size_t ordinal) {
417 auto block = tensor.block_by_ordinal(ordinal);
418 detail::scale_block(block, factor);
419 });
420 }
421 else
422 {
423 for (std::size_t ordinal = 0; ordinal < tensor.stored_block_count(); ++ordinal)
424 {
425 uni20::transform_inplace(tensor.async_block_by_ordinal(ordinal), linalg::scale{factor});
426 }
427 }
428}
429
438template <MutableBlockTensorView Output, class Scalar, BlockTensorView Input>
439 requires detail::CompatibleBlockTensorValues<Output, Input> &&
440 detail::CompatibleLinearOutputRepresentation<Output, Input> &&
441 ((!MutableAsyncBlockTensorView<Output> && !AsyncBlockTensorView<Input>) ||
442 (MutableAsyncBlockTensorView<Output> && AsyncBlockTensorView<Input>))
443void assign_scale(Output& output, Scalar factor, Input const& input)
444{
445 detail::require_compatible_block_tensor_values(output, input);
446 detail::require_stored_key_superset(output, input);
447 if (detail::same_block_tensor_object(output, input))
448 {
449 scale(output, std::move(factor));
450 return;
451 }
452
453#if UNI20_BACKEND_CUDA
454 if constexpr (detail::CudaPartitionedBlockTensor<Output> && detail::CudaPartitionedBlockTensor<Input> &&
455 linalg::cuda_partitioned_scale_scalars<block_tensor_value_t<Output>, Scalar>)
456 {
457 if (detail::same_packed_layout(output, input))
458 {
459 if (!output.storage().has_padding() || uni20::isfinite(factor))
460 {
461 linalg::cuda_partitioned_assign_scale(output.storage().buffer(), factor, input.storage().buffer());
462 return;
463 }
464 }
465 }
466#endif
467
469 {
470 detail::execute_linear_block_batch(detail::block_execution_policy_t<Output>{}, output.stored_block_count(),
471 [&](std::size_t ordinal) {
472 auto output_block = output.block_by_ordinal(ordinal);
473 auto const& key = output.stored_keys()[ordinal];
474 if (auto input_ordinal = detail::find_stored_key_ordinal(input, key))
475 {
476 auto input_block = input.block_by_ordinal(*input_ordinal);
477 detail::assign_scale_block(output_block, factor, input_block);
478 }
479 else
480 detail::set_zero_block(output_block);
481 });
482 }
483 else
484 {
485 using value_type = block_tensor_value_t<Output>;
486 for (std::size_t ordinal = 0; ordinal < output.stored_block_count(); ++ordinal)
487 {
488 auto& output_block = output.async_block_by_ordinal(ordinal);
489 auto const& key = output.stored_keys()[ordinal];
490 if (auto const input_ordinal = std::ranges::lower_bound(input.stored_keys(), key);
491 input_ordinal != input.stored_keys().end() && *input_ordinal == key)
492 {
493 auto const index = static_cast<std::size_t>(input_ordinal - input.stored_keys().begin());
494 uni20::assign_transform(output_block, linalg::scale{factor}, input.async_block_by_ordinal(index));
495 }
496 else
497 {
498 uni20::fill(output_block, value_type{});
499 }
500 }
501 }
502}
503
509template <MutableBlockTensorView Output, BlockTensorView Input>
510 requires detail::CompatibleBlockTensorValues<Output, Input> &&
511 detail::CompatibleLinearOutputRepresentation<Output, Input> &&
512 ((!MutableAsyncBlockTensorView<Output> && !AsyncBlockTensorView<Input>) ||
513 (MutableAsyncBlockTensorView<Output> && AsyncBlockTensorView<Input>))
514void copy(Output& output, Input const& input)
515{
516 if (detail::same_block_tensor_object(output, input)) return;
517 using value_type = block_tensor_value_t<Output>;
518 assign_scale(output, value_type{1}, input);
519}
520
528template <MutableBlockTensorView Output, BlockTensorView Lhs, BlockTensorView Rhs>
529 requires detail::CompatibleBlockTensorValues<Output, Lhs> && detail::CompatibleBlockTensorValues<Output, Rhs> &&
530 detail::CompatibleLinearOutputRepresentation<Output, Lhs, Rhs> &&
531 ((!MutableAsyncBlockTensorView<Output> && !AsyncBlockTensorView<Lhs> && !AsyncBlockTensorView<Rhs>) ||
532 (MutableAsyncBlockTensorView<Output> && AsyncBlockTensorView<Lhs> && AsyncBlockTensorView<Rhs>))
533void add(Output& output, Lhs const& lhs, Rhs const& rhs)
534{
535 detail::require_compatible_block_tensor_values(output, lhs);
536 detail::require_compatible_block_tensor_values(output, rhs);
537 detail::require_stored_key_superset(output, lhs);
538 detail::require_stored_key_superset(output, rhs);
539
540 if (detail::same_block_tensor_object(output, lhs))
541 {
542 if (detail::same_block_tensor_object(output, rhs))
543 {
544 using value_type = block_tensor_value_t<Output>;
545 scale(output, value_type{2});
546 }
547 else
548 {
549 add_inplace(output, rhs);
550 }
551 return;
552 }
553 if (detail::same_block_tensor_object(output, rhs))
554 {
555 add_inplace(output, lhs);
556 return;
557 }
558
560 {
561 detail::execute_linear_block_batch(detail::block_execution_policy_t<Output>{}, output.stored_block_count(),
562 [&](std::size_t ordinal) {
563 auto output_block = output.block_by_ordinal(ordinal);
564 auto const& key = output.stored_keys()[ordinal];
565 auto lhs_ordinal = detail::find_stored_key_ordinal(lhs, key);
566 auto rhs_ordinal = detail::find_stored_key_ordinal(rhs, key);
567 if (lhs_ordinal && rhs_ordinal)
568 {
569 auto lhs_block = lhs.block_by_ordinal(*lhs_ordinal);
570 auto rhs_block = rhs.block_by_ordinal(*rhs_ordinal);
571 detail::add_block(output_block, lhs_block, rhs_block);
572 }
573 else if (lhs_ordinal)
574 {
575 auto lhs_block = lhs.block_by_ordinal(*lhs_ordinal);
576 uni20::copy(output_block, lhs_block);
577 }
578 else if (rhs_ordinal)
579 {
580 auto rhs_block = rhs.block_by_ordinal(*rhs_ordinal);
581 uni20::copy(output_block, rhs_block);
582 }
583 else
584 detail::set_zero_block(output_block);
585 });
586 }
587 else
588 {
589 using value_type = block_tensor_value_t<Output>;
590 for (std::size_t ordinal = 0; ordinal < output.stored_block_count(); ++ordinal)
591 {
592 auto& output_block = output.async_block_by_ordinal(ordinal);
593 auto const& key = output.stored_keys()[ordinal];
594 auto const lhs_found = std::ranges::lower_bound(lhs.stored_keys(), key);
595 auto const rhs_found = std::ranges::lower_bound(rhs.stored_keys(), key);
596 bool const has_lhs = lhs_found != lhs.stored_keys().end() && *lhs_found == key;
597 bool const has_rhs = rhs_found != rhs.stored_keys().end() && *rhs_found == key;
598 if (has_lhs && has_rhs)
599 {
600 auto const lhs_ordinal = static_cast<std::size_t>(lhs_found - lhs.stored_keys().begin());
601 auto const rhs_ordinal = static_cast<std::size_t>(rhs_found - rhs.stored_keys().begin());
602 uni20::assign_transform(output_block, linalg::add{}, lhs.async_block_by_ordinal(lhs_ordinal),
603 rhs.async_block_by_ordinal(rhs_ordinal));
604 }
605 else if (has_lhs)
606 {
607 auto const lhs_ordinal = static_cast<std::size_t>(lhs_found - lhs.stored_keys().begin());
608 uni20::assign_transform(output_block, linalg::scale{value_type{1}}, lhs.async_block_by_ordinal(lhs_ordinal));
609 }
610 else if (has_rhs)
611 {
612 auto const rhs_ordinal = static_cast<std::size_t>(rhs_found - rhs.stored_keys().begin());
613 uni20::assign_transform(output_block, linalg::scale{value_type{1}}, rhs.async_block_by_ordinal(rhs_ordinal));
614 }
615 else
616 {
617 uni20::fill(output_block, value_type{});
618 }
619 }
620 }
621}
622
630template <MutableBlockTensorView Output, BlockTensorView Input>
631 requires detail::CompatibleBlockTensorValues<Output, Input> &&
632 detail::CompatibleLinearOutputRepresentation<Output, Input> &&
633 ((!MutableAsyncBlockTensorView<Output> && !AsyncBlockTensorView<Input>) ||
634 (MutableAsyncBlockTensorView<Output> && AsyncBlockTensorView<Input>))
635void add_inplace(Output& output, Input const& input)
636{
637 detail::require_compatible_block_tensor_values(output, input);
638 detail::require_stored_key_superset(output, input);
639 if (detail::same_block_tensor_object(output, input))
640 {
641 using value_type = block_tensor_value_t<Output>;
642 scale(output, value_type{2});
643 return;
644 }
645#if UNI20_BACKEND_CUDA
646 if constexpr (detail::CudaPartitionedBlockTensor<Output> && detail::CudaPartitionedBlockTensor<Input> &&
647 linalg::cuda_partitioned_scale_scalars<block_tensor_value_t<Output>, block_tensor_value_t<Output>>)
648 {
649 if (detail::same_packed_layout(output, input))
650 {
651 using value_type = block_tensor_value_t<Output>;
652 linalg::cuda_partitioned_axpy(output.storage().buffer(), value_type{1}, input.storage().buffer());
653 return;
654 }
655 }
656#endif
657
659 {
660 detail::execute_linear_block_batch(detail::block_execution_policy_t<Output>{}, input.stored_block_count(),
661 [&](std::size_t input_ordinal) {
662 auto const& key = input.stored_keys()[input_ordinal];
663 auto const output_ordinal = *detail::find_stored_key_ordinal(output, key);
664 auto output_block = output.block_by_ordinal(output_ordinal);
665 auto input_block = input.block_by_ordinal(input_ordinal);
666 detail::add_inplace_block(output_block, input_block);
667 });
668 }
669 else
670 {
671 for (std::size_t input_ordinal = 0; input_ordinal < input.stored_block_count(); ++input_ordinal)
672 {
673 auto const& key = input.stored_keys()[input_ordinal];
674 auto const output_found = std::ranges::lower_bound(output.stored_keys(), key);
675 auto const output_ordinal = static_cast<std::size_t>(output_found - output.stored_keys().begin());
676 uni20::transform_inplace(output.async_block_by_ordinal(output_ordinal), linalg::add{},
677 input.async_block_by_ordinal(input_ordinal));
678 }
679 }
680}
681
687template <MutableBlockTensorView Output, class Scalar, BlockTensorView Input>
688 requires detail::CompatibleBlockTensorValues<Output, Input> &&
689 detail::CompatibleLinearOutputRepresentation<Output, Input> &&
690 ((!MutableAsyncBlockTensorView<Output> && !AsyncBlockTensorView<Input>) ||
691 (MutableAsyncBlockTensorView<Output> && AsyncBlockTensorView<Input>))
692void axpy(Output& output, Scalar factor, Input const& input)
693{
694 detail::require_compatible_block_tensor_values(output, input);
695 detail::require_stored_key_superset(output, input);
696 if (detail::same_block_tensor_object(output, input))
697 {
698 using value_type = block_tensor_value_t<Output>;
699 scale(output, value_type{1} + factor);
700 return;
701 }
702#if UNI20_BACKEND_CUDA
703 if constexpr (detail::CudaPartitionedBlockTensor<Output> && detail::CudaPartitionedBlockTensor<Input> &&
704 linalg::cuda_partitioned_scale_scalars<block_tensor_value_t<Output>, Scalar>)
705 {
706 if (detail::same_packed_layout(output, input))
707 {
708 if (!output.storage().has_padding() || uni20::isfinite(factor))
709 {
710 linalg::cuda_partitioned_axpy(output.storage().buffer(), factor, input.storage().buffer());
711 return;
712 }
713 }
714 }
715#endif
716
718 {
719 detail::execute_linear_block_batch(detail::block_execution_policy_t<Output>{}, input.stored_block_count(),
720 [&](std::size_t input_ordinal) {
721 auto const& key = input.stored_keys()[input_ordinal];
722 auto const output_ordinal = *detail::find_stored_key_ordinal(output, key);
723 auto output_block = output.block_by_ordinal(output_ordinal);
724 auto input_block = input.block_by_ordinal(input_ordinal);
725 detail::axpy_block(output_block, factor, input_block);
726 });
727 }
728 else
729 {
730 for (std::size_t input_ordinal = 0; input_ordinal < input.stored_block_count(); ++input_ordinal)
731 {
732 auto const& key = input.stored_keys()[input_ordinal];
733 auto const output_found = std::ranges::lower_bound(output.stored_keys(), key);
734 auto const output_ordinal = static_cast<std::size_t>(output_found - output.stored_keys().begin());
735 uni20::transform_inplace(output.async_block_by_ordinal(output_ordinal), linalg::add_scaled{factor},
736 input.async_block_by_ordinal(input_ordinal));
737 }
738 }
739}
740
747template <class OutputStorage = void, BlockTensorView Lhs, BlockTensorView Rhs>
748 requires detail::CompatibleBlockTensorValues<Lhs, Rhs> &&
749 SparseBlockStorage<detail::selected_linear_storage_t<OutputStorage, Lhs>> &&
750 detail::CompatibleLinearOutputStorage<detail::selected_linear_storage_t<OutputStorage, Lhs>, Lhs, Rhs>
751[[nodiscard]] auto add(Lhs const& lhs, Rhs const& rhs)
752{
753 detail::require_compatible_block_tensor_values(lhs, rhs);
754 using storage_type = detail::selected_linear_storage_t<OutputStorage, Lhs>;
755 using lhs_type = block_tensor_type_t<Lhs>;
756 using result_type = BlockTensor<typename lhs_type::value_type, typename lhs_type::domain_type,
757 typename lhs_type::codomain_type, storage_type>;
758 auto result =
759 result_type(uninitialized, lhs.symmetry(), lhs.domain(), lhs.codomain(), detail::stored_key_union(lhs, rhs));
760 add(result, lhs, rhs);
761 return result;
762}
763
770template <BlockTensorView Lhs, BlockTensorView Rhs>
771 requires detail::CompatibleBlockTensorValues<Lhs, Rhs> && RealOrComplex<block_tensor_value_t<Lhs>> &&
772 ((!AsyncBlockTensorView<Lhs> && !AsyncBlockTensorView<Rhs>) ||
773 (AsyncBlockTensorView<Lhs> && AsyncBlockTensorView<Rhs>))
774[[nodiscard]] auto inner_product_host(Lhs const& lhs, Rhs const& rhs) -> block_tensor_value_t<Lhs>
775{
776 detail::require_compatible_block_tensor_values(lhs, rhs);
777 using value_type = block_tensor_value_t<Lhs>;
778#if UNI20_BACKEND_CUBLAS
779 if constexpr (detail::CudaPartitionedBlockTensor<Lhs> && detail::CudaPartitionedBlockTensor<Rhs> &&
781 {
782 if (detail::same_packed_layout(lhs, rhs))
783 {
784 auto const size = lhs.storage().buffer().size();
785 if (linalg::cuda_partitioned_reduction_size_supported(size))
786 return linalg::cuda_partitioned_inner_product_host(lhs.storage().buffer(), rhs.storage().buffer());
787 }
788 }
789#endif
790 auto const bindings = detail::stored_key_intersection_bindings(lhs, rhs);
791 std::vector<value_type> partials(bindings.size());
792 if constexpr (!AsyncBlockTensorView<Lhs>)
793 {
794 detail::execute_linear_block_batch(detail::reduction_execution_policy_t<Lhs, Rhs>{}, bindings.size(),
795 [&](std::size_t index) {
796 auto const& binding = bindings[index];
797 auto lhs_block = lhs.block_by_ordinal(binding.lhs_ordinal);
798 auto rhs_block = rhs.block_by_ordinal(binding.rhs_ordinal);
799 partials[index] = detail::inner_product_block_host(lhs_block, rhs_block);
800 });
801 }
802 else
803 {
804 for (std::size_t index = 0; index < bindings.size(); ++index)
805 {
806 auto const& binding = bindings[index];
807 auto lhs_read = lhs.async_block_by_ordinal(binding.lhs_ordinal).read();
808 auto rhs_read = rhs.async_block_by_ordinal(binding.rhs_ordinal).read();
809 partials[index] = detail::inner_product_block_host(lhs_read.get_wait(), rhs_read.get_wait());
810 }
811 }
812
813 value_type result{};
814 value_type compensation{};
815 for (auto const& partial : partials)
816 {
817 if (!uni20::isfinite(result) || !uni20::isfinite(partial))
818 {
819 result += partial;
820 compensation = value_type{};
821 continue;
822 }
823 auto const corrected = partial - compensation;
824 auto const updated = result + corrected;
825 compensation = (updated - result) - corrected;
826 result = updated;
827 }
828 return result;
829}
830
833template <BlockTensorView Lhs, BlockTensorView Rhs>
834 requires detail::CompatibleBlockTensorValues<Lhs, Rhs> && RealOrComplex<block_tensor_value_t<Lhs>> &&
835 ((!AsyncBlockTensorView<Lhs> && !AsyncBlockTensorView<Rhs>) ||
836 (AsyncBlockTensorView<Lhs> && AsyncBlockTensorView<Rhs>))
837[[nodiscard]] auto inner_product(Lhs const& lhs, Rhs const& rhs)
838{
839 using value_type = block_tensor_value_t<Lhs>;
841 result[] = inner_product_host(lhs, rhs);
842 return result;
843}
844
848template <BlockTensorView Tensor>
849 requires RealOrComplex<block_tensor_value_t<Tensor>>
850[[nodiscard]] auto norm_host(Tensor const& tensor) -> make_real_t<block_tensor_value_t<Tensor>>
851{
852#if UNI20_BACKEND_CUBLAS
853 if constexpr (detail::CudaPartitionedBlockTensor<Tensor> &&
855 {
856 auto const size = tensor.storage().buffer().size();
857 if (linalg::cuda_partitioned_reduction_size_supported(size))
858 return linalg::cuda_partitioned_norm_host(tensor.storage().buffer());
859 }
860#endif
862 std::vector<real_type> partials(tensor.stored_block_count());
863 if constexpr (!AsyncBlockTensorView<Tensor>)
864 {
865 detail::execute_linear_block_batch(detail::block_execution_policy_t<Tensor>{}, tensor.stored_block_count(),
866 [&](std::size_t ordinal) {
867 auto block = tensor.block_by_ordinal(ordinal);
868 partials[ordinal] = detail::norm_block_host(block);
869 });
870 }
871 else
872 {
873 for (std::size_t ordinal = 0; ordinal < tensor.stored_block_count(); ++ordinal)
874 {
875 auto read = tensor.async_block_by_ordinal(ordinal).read();
876 partials[ordinal] = detail::norm_block_host(read.get_wait());
877 }
878 }
879
880 real_type scale{};
881 real_type scaled_sum{};
882 for (auto const partial : partials)
883 {
884 if (partial == real_type{}) continue;
885 if (!uni20::isfinite(partial)) return partial;
886 if (scale < partial)
887 {
888 auto const ratio = scale / partial;
889 scaled_sum = real_type{1} + scaled_sum * ratio * ratio;
890 scale = partial;
891 }
892 else
893 {
894 auto const ratio = partial / scale;
895 scaled_sum += ratio * ratio;
896 }
897 }
898 if (scale == real_type{}) return real_type{};
899 using std::sqrt;
900 return scale * sqrt(scaled_sum);
901}
902
904template <BlockTensorView Tensor>
905 requires RealOrComplex<block_tensor_value_t<Tensor>>
906[[nodiscard]] auto norm(Tensor const& tensor)
907{
910 result[] = norm_host(tensor);
911 return result;
912}
913
914} // namespace uni20
Defines the first bosonic abelian BlockTensor container.
Concepts and associated types for BlockTensor-level views.
Fixed-order tensor whose blocks obey a bosonic abelian selection rule.
Definition block_tensor.hpp:161
General-purpose owning tensor with immediate or deferred mdspan metadata.
Definition basic_tensor.hpp:109
storage_type & storage() noexcept
Access the owned storage container.
Definition basic_tensor.hpp:542
BlockTensorView exposing an independently scheduled read timeline per block.
Definition block_tensor_concepts.hpp:107
MutableBlockTensorView exposing an independently scheduled write timeline per block.
Definition block_tensor_concepts.hpp:114
Concept for numeric scalar types (integer, real, or complex).
Definition scalar_concepts.hpp:81
Scalar types supported by the cuBLAS level-one wrappers.
Definition level1.hpp:27
Backend-dispatched copies into existing mdspan and tensor outputs.
Coroutine scheduler for AsyncTask.
Named backend-neutral function objects for elementwise transforms.
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
Async Tensor wrappers for elementwise overwrite and update operations.
Scalar math helper utilities.
Mdspan-shaped metadata for storage whose data handle requires acquisition.
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
void cuda_partitioned_axpy(cuda::PartitionedCudaBuffer< Scalar > &output, Factor factor, cuda::PartitionedCudaBuffer< Scalar > const &input)
Add a scaled partitioned CUDA allocation to another in place.
Definition partitioned_buffer_linear.hpp:142
void cuda_partitioned_set_zero(cuda::PartitionedCudaBuffer< Scalar > &output)
Set a complete partitioned CUDA allocation to numerical zero.
Definition partitioned_buffer_linear.hpp:84
void cuda_partitioned_scale(cuda::PartitionedCudaBuffer< Scalar > &output, Factor factor)
Scale a complete partitioned CUDA allocation in place.
Definition partitioned_buffer_linear.hpp:102
void cuda_partitioned_assign_scale(cuda::PartitionedCudaBuffer< Scalar > &output, Factor factor, cuda::PartitionedCudaBuffer< Scalar > const &input)
Overwrite one partitioned CUDA allocation with a scaled peer allocation.
Definition partitioned_buffer_linear.hpp:120
typename optional_traits< T >::value_type value_type
Definition data_table.hpp:42
Definition async/async.hpp:26
constexpr uninitialized_t uninitialized
Explicit allocation tag for overwrite outputs and scratch space.
Definition common/initialization.hpp:22
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
void inner_product(BackendSelector selector, async::Async< OutputTensor > &output, async::Async< LhsTensor > const &lhs, async::Async< RhsTensor > const &rhs)
Schedule an inner product into an explicit async scalar Tensor.
Definition linalg/async/reductions.hpp:455
exact_rational sqrt(X const &value)
Return the nonnegative rational square root, or require finite precision.
Definition exact_rational.hpp:355
void assign_scale(Output &output, Scalar factor, Input const &input)
Overwrite a fixed BlockTensor output with a scaled input value.
Definition block_tensor_linear.hpp:443
mpreal norm(mpcomplex const &z)
Definition mpcomplex.hpp:494
auto inner_product_host(BackendSelector selector, async::Async< LhsTensor > const &lhs, async::Async< RhsTensor > const &rhs)
Return a nonblocking async host C++ scalar inner product through an explicit selector.
Definition linalg/async/reductions.hpp:496
void axpy(Output &output, Scalar factor, Input const &input)
Compute output += factor * input without changing either key pattern.
Definition block_tensor_linear.hpp:692
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 norm_host(BackendSelector selector, async::Async< InputTensor > const &input)
Return a nonblocking async host C++ scalar norm through an explicit selector.
Definition linalg/async/reductions.hpp:554
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
void scale(Tensor &tensor, Scalar factor)
Multiply every stored numerical block by a scalar in place.
Definition block_tensor_linear.hpp:400
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
void add_inplace(Output &output, Input const &input)
Add one BlockTensor value to an existing output in place.
Definition block_tensor_linear.hpp:635
std::ptrdiff_t index_type
Definition types.hpp:19
int add(int a, int b)
Definition dummy.cpp:6
Tensor(InputTensor const &) -> Tensor< tensor_element_t< InputTensor >, tensor_mdspec_t< InputTensor >::rank(), HostStorage, detail::materialized_layout_t< void, tensor_mdspec_t< InputTensor > >, DefaultAccessorFactory >
Deduce a runtime-extents host tensor that materializes a tensor view.
void transform_inplace(BackendSelector selector, async::Async< OutputTensor > &output, Function &&function, async::Async< InputTensors > const &... inputs)
Schedule a variadic elementwise update with an explicit backend selector.
Definition linalg/async/transform.hpp:258
constexpr auto diagonal_components(stdex::mdspan< ElementType, Extents, LayoutPolicy, diagonal_accessor< ElementType, AccessorExtents > > const &span)
Expose the physically stored values of a generalized-diagonal mdspan.
Definition diagonal_accessor.hpp:252
void set_zero(Tensor &tensor)
Set every stored numerical block to zero without changing structure.
Definition block_tensor_linear.hpp:369
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
Allocation-wide CUDA linear operations for partitioned buffers.
std::size_t span
Definition presentation.cpp:2017
Add one retained scalar multiple to an existing element value.
Definition elementwise_functions.hpp:86
Return the sum of two element values.
Definition elementwise_functions.hpp:102
Multiply one element value by a retained scalar factor.
Definition elementwise_functions.hpp:68
Tensor sums, inner products, and Euclidean norms with explicit result residency.
Backend-dispatched elementwise overwrite and update operations.