Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
linalg/cpu/contract.hpp
Go to the documentation of this file.
1#pragma once
2
15
16#include <algorithm>
17#include <array>
18#include <concepts>
19#include <cstddef>
20#include <type_traits>
21#include <utility>
22
23namespace uni20::linalg::cpu
24{
25namespace detail
26{
27
28template <class Span, class Scalar, std::size_t... Axis>
29consteval bool contraction_input_expressions(std::index_sequence<Axis...>)
30{
31 using span_type = std::remove_cvref_t<Span>;
32 return requires(Span& span, typename span_type::index_type index) {
33 static_cast<Scalar>(span.operator[](((void)Axis, index)...));
34 };
35}
36
37template <class Span, class Scalar, std::size_t... Axis>
38consteval bool contraction_output_expressions(std::index_sequence<Axis...>)
39{
40 using span_type = std::remove_cvref_t<Span>;
41 return requires(Span& span, typename span_type::index_type index, Scalar value) {
42 static_cast<Scalar>(span.operator[](((void)Axis, index)...));
43 span.operator[](((void)Axis, index)...) = value;
44 };
45}
46
47template <class Span, class Scalar>
48concept ReadableDiagonalComponents =
50 uni20::RankedMdspanLike<decltype(diagonal_components(std::declval<std::remove_cvref_t<Span> const&>())), 1> &&
51 contraction_input_expressions<decltype(diagonal_components(std::declval<std::remove_cvref_t<Span> const&>())),
52 Scalar>(std::make_index_sequence<1>{});
53
54template <class OutputMdspan, class LhsMdspan, class RhsMdspan, std::size_t LhsRank, std::size_t RhsRank,
55 std::size_t ContractedRank>
56consteval bool contraction_preserves_output_structure()
57{
59 if constexpr (!uni20::DiagonalMdspecLike<OutputMdspan> || output_rank <= 1)
60 return true;
61 else if constexpr (LhsRank == 0)
63 else if constexpr (RhsRank == 0)
65 else
67}
68
69template <class Span, std::size_t Rank, std::size_t... Axis>
70constexpr decltype(auto) element_at(Span& span, std::array<uni20::index_type, Rank> const& indices,
71 std::index_sequence<Axis...>)
72{
73 using index_type = typename std::remove_cvref_t<Span>::index_type;
74 return span.operator[](static_cast<index_type>(indices[Axis])...);
75}
76
77template <class OutputMdspan, class Scalar, class LhsMdspan, class RhsMdspan, std::size_t LhsRank, std::size_t RhsRank,
78 std::size_t ContractedRank>
79class ContractionLoop {
80 public:
81 using axes_type = ContractionAxes<LhsRank, RhsRank, ContractedRank>;
82 static constexpr std::size_t output_rank = axes_type::output_rank;
83
84 ContractionLoop(OutputMdspan& output, Scalar alpha, LhsMdspan& lhs, RhsMdspan& rhs, Scalar beta,
85 axes_type const& axes)
86 : output_(output), alpha_(alpha), lhs_(lhs), rhs_(rhs), beta_(beta), axes_(axes)
87 {}
88
89 void run() { this->loop_output(0); }
90
91 private:
92 void loop_output(std::size_t axis)
93 {
94 if (axis == output_rank)
95 {
96 this->evaluate_output_element();
97 return;
98 }
99
100 auto const extent = static_cast<uni20::index_type>(output_.extent(axis));
101 for (uni20::index_type index = 0; index < extent; ++index)
102 {
103 output_indices_[axis] = index;
105 lhs_indices_[axes_.lhs_surviving[axis]] = index;
106 else
107 rhs_indices_[axes_.rhs_surviving[axis - axes_type::lhs_surviving_rank]] = index;
108 this->loop_output(axis + 1);
109 }
110 }
111
112 void evaluate_output_element()
113 {
114 Scalar product{};
115 if (alpha_ != Scalar{}) this->accumulate_contracted(0, product);
116
117 auto&& output_value = element_at(output_, output_indices_, std::make_index_sequence<output_rank>{});
118 if (beta_ == Scalar{})
119 output_value = alpha_ * product;
120 else
121 output_value = beta_ * static_cast<Scalar>(output_value) + alpha_ * product;
122 }
123
124 void accumulate_contracted(std::size_t axis, Scalar& product)
125 {
126 if (axis == ContractedRank)
127 {
128 product += static_cast<Scalar>(element_at(lhs_, lhs_indices_, std::make_index_sequence<LhsRank>{})) *
129 static_cast<Scalar>(element_at(rhs_, rhs_indices_, std::make_index_sequence<RhsRank>{}));
130 return;
131 }
132
133 auto const lhs_axis = axes_.lhs_contracted[axis];
134 auto const rhs_axis = axes_.rhs_contracted[axis];
135 auto const extent = static_cast<uni20::index_type>(lhs_.extent(lhs_axis));
136 for (uni20::index_type index = 0; index < extent; ++index)
137 {
138 lhs_indices_[lhs_axis] = index;
139 rhs_indices_[rhs_axis] = index;
140 this->accumulate_contracted(axis + 1, product);
141 }
142 }
143
144 OutputMdspan& output_;
145 Scalar alpha_;
146 LhsMdspan& lhs_;
147 RhsMdspan& rhs_;
148 Scalar beta_;
149 axes_type const& axes_;
150 std::array<uni20::index_type, output_rank> output_indices_{};
151 std::array<uni20::index_type, LhsRank> lhs_indices_{};
152 std::array<uni20::index_type, RhsRank> rhs_indices_{};
153};
154
155template <class OutputMdspan, class Scalar, class Product>
156void contract_matrix_elements(OutputMdspan& output, Scalar alpha, Scalar beta, Product&& product_at)
157{
158 for (uni20::index_type row = 0; row < static_cast<uni20::index_type>(output.extent(0)); ++row)
159 {
160 for (uni20::index_type column = 0; column < static_cast<uni20::index_type>(output.extent(1)); ++column)
161 {
162 Scalar product{};
163 if (alpha != Scalar{}) product = product_at(row, column);
164
165 std::array<uni20::index_type, 2> const output_indices{row, column};
166 auto&& output_value = element_at(output, output_indices, std::make_index_sequence<2>{});
167 if (beta == Scalar{})
168 output_value = alpha * product;
169 else
170 output_value = beta * static_cast<Scalar>(output_value) + alpha * product;
171 }
172 }
173}
174
175template <class OutputMdspan, class Scalar, class LhsMdspan, class RhsMdspan>
176void contract_two_diagonal_matrices(OutputMdspan& output, Scalar alpha, LhsMdspan& lhs, RhsMdspan& rhs, Scalar beta)
177{
178 auto const lhs_components = diagonal_components(lhs);
179 auto const rhs_components = diagonal_components(rhs);
180 auto update_components = [&](auto output_components) {
181 auto const extent = std::min({static_cast<uni20::index_type>(output_components.extent(0)),
182 static_cast<uni20::index_type>(lhs_components.extent(0)),
183 static_cast<uni20::index_type>(rhs_components.extent(0))});
184 for (uni20::index_type index = 0; index < static_cast<uni20::index_type>(output_components.extent(0)); ++index)
185 {
186 auto&& output_value = output_components[index];
187 if (beta == Scalar{})
188 output_value = Scalar{};
189 else
190 output_value = beta * static_cast<Scalar>(output_value);
191 }
192 if (alpha == Scalar{}) return;
193 for (uni20::index_type index = 0; index < extent; ++index)
194 output_components[index] =
195 static_cast<Scalar>(output_components[index]) +
196 alpha * static_cast<Scalar>(lhs_components[index]) * static_cast<Scalar>(rhs_components[index]);
197 };
198
200 {
201 update_components(diagonal_components(output));
202 }
203 else
204 {
205 for (uni20::index_type row = 0; row < static_cast<uni20::index_type>(output.extent(0)); ++row)
206 {
207 for (uni20::index_type column = 0; column < static_cast<uni20::index_type>(output.extent(1)); ++column)
208 {
209 std::array<uni20::index_type, 2> const indices{row, column};
210 auto&& output_value = element_at(output, indices, std::make_index_sequence<2>{});
211 if (beta == Scalar{})
212 output_value = Scalar{};
213 else
214 output_value = beta * static_cast<Scalar>(output_value);
215 }
216 }
217 auto const extent =
218 std::min({static_cast<uni20::index_type>(output.extent(0)), static_cast<uni20::index_type>(output.extent(1)),
219 static_cast<uni20::index_type>(lhs_components.extent(0)),
220 static_cast<uni20::index_type>(rhs_components.extent(0))});
221 if (alpha == Scalar{}) return;
222 for (uni20::index_type index = 0; index < extent; ++index)
223 output[index, index] =
224 static_cast<Scalar>(output[index, index]) +
225 alpha * static_cast<Scalar>(lhs_components[index]) * static_cast<Scalar>(rhs_components[index]);
226 }
227}
228
229template <class OutputMdspan, class Scalar, class LhsMdspan, class RhsMdspan>
230void contract_diagonal_matrices(OutputMdspan& output, Scalar alpha, LhsMdspan& lhs, RhsMdspan& rhs, Scalar beta,
231 ContractionAxes<2, 2, 1> const& axes)
232{
233 if constexpr (ReadableDiagonalComponents<LhsMdspan, Scalar> && ReadableDiagonalComponents<RhsMdspan, Scalar>)
234 {
235 contract_two_diagonal_matrices(output, alpha, lhs, rhs, beta);
236 }
237 else if constexpr (ReadableDiagonalComponents<LhsMdspan, Scalar>)
238 {
239 auto const components = diagonal_components(lhs);
240 contract_matrix_elements(output, alpha, beta, [&](uni20::index_type row, uni20::index_type column) -> Scalar {
241 if (row >= static_cast<uni20::index_type>(components.extent(0))) return {};
242 std::array<uni20::index_type, 2> rhs_indices{};
243 rhs_indices[axes.rhs_contracted[0]] = row;
244 rhs_indices[axes.rhs_surviving[0]] = column;
245 return static_cast<Scalar>(components[static_cast<typename decltype(components)::index_type>(row)]) *
246 static_cast<Scalar>(element_at(rhs, rhs_indices, std::make_index_sequence<2>{}));
247 });
248 }
249 else
250 {
251 auto const components = diagonal_components(rhs);
252 contract_matrix_elements(output, alpha, beta, [&](uni20::index_type row, uni20::index_type column) -> Scalar {
253 if (column >= static_cast<uni20::index_type>(components.extent(0))) return {};
254 std::array<uni20::index_type, 2> lhs_indices{};
255 lhs_indices[axes.lhs_surviving[0]] = row;
256 lhs_indices[axes.lhs_contracted[0]] = column;
257 return static_cast<Scalar>(element_at(lhs, lhs_indices, std::make_index_sequence<2>{})) *
258 static_cast<Scalar>(components[static_cast<typename decltype(components)::index_type>(column)]);
259 });
260 }
261}
262
263} // namespace detail
264
266template <class OutputMdspan, class Scalar, class LhsMdspan, class RhsMdspan, std::size_t LhsRank, std::size_t RhsRank,
267 std::size_t ContractedRank>
272 std::same_as<std::remove_cv_t<typename std::remove_cvref_t<OutputMdspan>::element_type>, Scalar> &&
273 detail::contraction_output_expressions<OutputMdspan, Scalar>(
275 detail::contraction_input_expressions<LhsMdspan, Scalar>(std::make_index_sequence<LhsRank>{}) &&
276 detail::contraction_input_expressions<RhsMdspan, Scalar>(std::make_index_sequence<RhsRank>{}) &&
277 detail::contraction_preserves_output_structure<OutputMdspan, LhsMdspan, RhsMdspan, LhsRank, RhsRank,
278 ContractedRank>() &&
279 requires(Scalar value) {
280 value += value * value;
281 { value == Scalar{} } -> std::convertible_to<bool>;
282 { value != Scalar{} } -> std::convertible_to<bool>;
283 };
284
291template <class OutputMdspan, uni20::Scalar Scalar, class LhsMdspan, class RhsMdspan, std::size_t LhsRank,
292 std::size_t RhsRank, std::size_t ContractedRank>
293 requires ContractionCompatible<OutputMdspan, Scalar, LhsMdspan, RhsMdspan, LhsRank, RhsRank, ContractedRank>
294void contract(OutputMdspan& output, Scalar alpha, LhsMdspan& lhs, RhsMdspan& rhs, Scalar beta,
296{
298 auto const required_extents = contraction_output_extents(lhs, rhs, axes);
299 CHECK(uni20::tensor_extents_equal(output.extents(), required_extents),
300 "contraction output extents do not match the surviving input axes");
301
302 if constexpr (LhsRank == 2 && RhsRank == 2 && ContractedRank == 1 &&
303 (detail::ReadableDiagonalComponents<LhsMdspan, Scalar> ||
304 detail::ReadableDiagonalComponents<RhsMdspan, Scalar>))
305 {
306 detail::contract_diagonal_matrices(output, alpha, lhs, rhs, beta, axes);
307 }
308 else
309 {
310 detail::ContractionLoop<OutputMdspan, Scalar, LhsMdspan, RhsMdspan, LhsRank, RhsRank, ContractedRank> loop{
311 output, alpha, lhs, rhs, beta, axes};
312 loop.run();
313 }
314}
315
316} // namespace uni20::linalg::cpu
Full logical mdspec with a rank-one view of its generalized diagonal components.
Definition mdspan/concepts.hpp:465
Writable generalized-diagonal mdspec with writable rank-one components.
Definition mdspan/concepts.hpp:477
A mutable mdspan-like type with a specified static rank.
Definition mdspan/concepts.hpp:494
An mdspan-like type with a specified static rank.
Definition mdspan/concepts.hpp:447
Concept for numeric scalar types (integer, real, or complex).
Definition scalar_concepts.hpp:81
Report whether resolved mdspans support reference contraction expressions.
Definition linalg/cpu/contract.hpp:268
Normalized axis metadata for fixed-rank pairwise tensor contractions.
locale_t value
Definition data_table.cpp:49
Accessor support for compressed generalized diagonal tensors.
Mdspan concept and accessor extensions for Uni20.
T product(std::vector< T > const &values)
Definition mpreal_exact_example.cpp:13
Definition strided_transform.hpp:20
void contract(OutputMdspan &output, Scalar alpha, LhsMdspan &lhs, RhsMdspan &rhs, Scalar beta, ContractionAxes< LhsRank, RhsRank, ContractedRank > const &axes)
Execute a fixed-output pairwise contraction over resolved host mdspans.
Definition linalg/cpu/contract.hpp:294
KernelAttempt run(X &x, A &a, B &b, std::span< make_real_t< typename A::value_type > > residuals, LeastSquaresInfo &info, LeastSquaresOptions< make_real_t< typename A::value_type > > const &options, Precision p, Factor factor)
Definition least_squares_common.hpp:92
auto contraction_output_extents(Lhs const &lhs, Rhs const &rhs, ContractionAxes< LhsRank, RhsRank, ContractedRank > const &axes) -> stdex::dextents< uni20::index_type, ContractionAxes< LhsRank, RhsRank, ContractedRank >::output_rank >
Validate paired extents and return the canonical contraction output shape.
Definition contraction_axes.hpp:141
constexpr bool contraction_axes_are_valid(ContractionAxes< LhsRank, RhsRank, ContractedRank > const &axes) noexcept
Return whether contraction axes form canonical partitions of both inputs.
Definition contraction_axes.hpp:46
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
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
constexpr bool tensor_extents_equal(LhsExtents const &lhs, RhsExtents const &rhs) noexcept
Compare two tensor extent objects by rank and per-axis extent.
Definition tensor/output.hpp:31
std::size_t span
Definition presentation.cpp:2017
Canonical contracted and surviving axes for two fixed-rank operands.
Definition contraction_axes.hpp:26
static constexpr std::size_t output_rank
Definition contraction_axes.hpp:35
static constexpr std::size_t lhs_surviving_rank
Definition contraction_axes.hpp:33
Shape preparation contracts for tensor operation outputs.
#define CHECK(cond,...)
Definition trace.hpp:299