Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
symmetryimpl.hpp
Go to the documentation of this file.
1
6#pragma once
7
10
11#include <cctype>
12#include <cstdint>
13#include <mutex>
14#include <optional>
15#include <span>
16#include <sstream>
17#include <stdexcept>
18#include <string>
19#include <string_view>
20#include <unordered_map>
21#include <utility>
22#include <vector>
23
24namespace uni20::detail
25{
26
28inline constexpr std::uint64_t qnum_out_of_line_mask = std::uint64_t{1} << 63;
29
31inline constexpr std::uint64_t qnum_inline_code_max = qnum_out_of_line_mask - 1;
32
34struct SymmetryFactorSpec
35{
36 std::string name;
37 SymmetryFactorBase const* factor = nullptr;
38};
39
47class SymmetryImpl {
48 public:
51 explicit SymmetryImpl(std::vector<SymmetryFactorSpec> factors) : factors_(std::move(factors))
52 {
53 for (std::size_t i = 0; i < factors_.size(); ++i)
54 {
55 auto const [it, inserted] = index_by_name_.emplace(factors_[i].name, i);
56 if (!inserted)
57 {
58 throw std::invalid_argument("duplicate symmetry component name: " + it->first);
59 }
60 }
61 canonical_string_ = build_canonical_string(factors_);
62 }
63
66 auto factors() const -> std::span<SymmetryFactorSpec const> { return factors_; }
67
70 auto factor_count() const -> std::size_t { return factors_.size(); }
71
75 auto find_factor(std::string_view name) const -> std::optional<std::size_t>
76 {
77 auto const it = index_by_name_.find(std::string{name});
78 if (it == index_by_name_.end())
79 {
80 return std::nullopt;
81 }
82 return it->second;
83 }
84
87 auto to_string() const -> std::string const& { return canonical_string_; }
88
92 static auto intern(std::vector<SymmetryFactorSpec> factors) -> SymmetryImpl const*
93 {
94 auto const key = build_canonical_string(factors);
95
96 auto& mutex = intern_mutex();
97 std::lock_guard<std::mutex> lock(mutex);
98
99 auto& map = intern_map();
100 if (auto const it = map.find(key); it != map.end())
101 {
102 return it->second;
103 }
104
105 auto* impl = new SymmetryImpl(std::move(factors));
106 map.emplace(impl->to_string(), impl);
107 return impl;
108 }
109
113 static auto parse(std::string_view spec) -> SymmetryImpl const*
114 {
115 register_builtin_symmetry_factors();
116
117 auto trimmed = trim(spec);
118 if (trimmed.empty())
119 {
120 throw std::invalid_argument("symmetry specification must not be empty");
121 }
122
123 std::vector<SymmetryFactorSpec> factors;
124 std::size_t cursor = 0;
125 while (cursor < trimmed.size())
126 {
127 auto const next = trimmed.find(',', cursor);
128 auto const token =
129 trim(trimmed.substr(cursor, next == std::string_view::npos ? std::string_view::npos : next - cursor));
130 if (token.empty())
131 {
132 throw std::invalid_argument("symmetry specification contains an empty factor");
133 }
134
135 auto const colon = token.find(':');
136 if (colon == std::string_view::npos)
137 {
138 throw std::invalid_argument("symmetry factor is missing ':' separator: " + std::string{token});
139 }
140
141 auto const name = trim(token.substr(0, colon));
142 auto const kind = trim(token.substr(colon + 1));
143 if (name.empty())
144 {
145 throw std::invalid_argument("symmetry factor has an empty name");
146 }
147 if (kind.empty())
148 {
149 throw std::invalid_argument("symmetry factor has an empty type");
150 }
151
152 factors.push_back({std::string{name}, find_symmetry_factor(kind)});
153
154 if (next == std::string_view::npos)
155 {
156 break;
157 }
158 cursor = next + 1;
159 }
160
161 return intern(std::move(factors));
162 }
163
167 auto pack(std::span<std::uint64_t const> factor_codes) const -> std::uint64_t
168 {
169 if (factor_codes.size() != factors_.size())
170 {
171 throw std::invalid_argument("wrong number of factor codes for symmetry packing");
172 }
173 if (factor_codes.empty())
174 {
175 return 0;
176 }
177
178 std::uint64_t combined = factor_codes.front();
179 if (combined > qnum_inline_code_max)
180 {
181 throw std::overflow_error("quantum number code requires the reserved out-of-line representation");
182 }
183 for (std::size_t i = 1; i < factor_codes.size(); ++i)
184 {
185 combined = cantor_pair(combined, factor_codes[i]);
186 }
187 return combined;
188 }
189
193 auto unpack(std::uint64_t code) const -> std::vector<std::uint64_t>
194 {
195 if (code > qnum_inline_code_max)
196 {
197 throw std::invalid_argument("cannot unpack a tagged out-of-line quantum number as an inline code");
198 }
199 if (factors_.empty())
200 {
201 return {};
202 }
203 if (factors_.size() == 1)
204 {
205 return {code};
206 }
207
208 std::vector<std::uint64_t> codes(factors_.size());
209 auto current = code;
210 for (std::size_t i = factors_.size(); i-- > 1;)
211 {
212 auto const [left, right] = cantor_unpair(current);
213 codes[i] = right;
214 current = left;
215 }
216 codes[0] = current;
217 return codes;
218 }
219
220 private:
224 static auto trim(std::string_view text) -> std::string_view
225 {
226 while (!text.empty() && std::isspace(static_cast<unsigned char>(text.front())) != 0)
227 {
228 text.remove_prefix(1);
229 }
230 while (!text.empty() && std::isspace(static_cast<unsigned char>(text.back())) != 0)
231 {
232 text.remove_suffix(1);
233 }
234 return text;
235 }
236
240 static auto build_canonical_string(std::span<SymmetryFactorSpec const> factors) -> std::string
241 {
242 std::ostringstream out;
243 for (std::size_t i = 0; i < factors.size(); ++i)
244 {
245 if (i != 0)
246 {
247 out << ',';
248 }
249 out << factors[i].name << ':' << factors[i].factor->type_name();
250 }
251 return out.str();
252 }
253
256 static auto intern_map() -> std::unordered_map<std::string, SymmetryImpl const*>&
257 {
258 // Canonical instances have process lifetime. Keep their registry alive
259 // too, so static destruction does not orphan the retained instances.
260 static auto* const map = new std::unordered_map<std::string, SymmetryImpl const*>;
261 return *map;
262 }
263
266 static auto intern_mutex() -> std::mutex&
267 {
268 static std::mutex mutex;
269 return mutex;
270 }
271
276 static auto triangular_leq(std::uint64_t value, std::uint64_t limit) -> bool
277 {
278 auto const left = (value % 2U == 0U) ? value / 2 : value / 2 + 1;
279 auto const right = (value % 2U == 0U) ? value + 1 : value;
280 if (left == 0)
281 {
282 return true;
283 }
284 return left <= limit / right;
285 }
286
290 static auto triangular(std::uint64_t value) -> std::uint64_t
291 {
292 auto const left = (value % 2U == 0U) ? value / 2 : value / 2 + 1;
293 auto const right = (value % 2U == 0U) ? value + 1 : value;
294 return left * right;
295 }
296
301 static auto cantor_pair(std::uint64_t left, std::uint64_t right) -> std::uint64_t
302 {
303 if (left > qnum_inline_code_max || right > qnum_inline_code_max || left > qnum_inline_code_max - right)
304 {
305 throw std::overflow_error("quantum number code requires the reserved out-of-line representation");
306 }
307
308 auto const sum = left + right;
309 if (!triangular_leq(sum, qnum_inline_code_max - right))
310 {
311 throw std::overflow_error("quantum number code requires the reserved out-of-line representation");
312 }
313 return triangular(sum) + right;
314 }
315
319 static auto cantor_unpair(std::uint64_t code) -> std::pair<std::uint64_t, std::uint64_t>
320 {
321 std::uint64_t lo = 0;
322 std::uint64_t hi = code;
323 while (lo < hi)
324 {
325 auto const mid = lo + (hi - lo + 1) / 2;
326 if (triangular_leq(mid, code))
327 {
328 lo = mid;
329 }
330 else
331 {
332 hi = mid - 1;
333 }
334 }
335
336 auto const sum = lo;
337 auto const triangle = triangular(sum);
338 auto const right = code - triangle;
339 auto const left = sum - right;
340 return {left, right};
341 }
342
343 std::vector<SymmetryFactorSpec> factors_;
344 std::unordered_map<std::string, std::size_t> index_by_name_;
345 std::string canonical_string_;
346};
347
348} // namespace uni20::detail
bool empty() const noexcept
Return whether this document has no spans.
Definition presentation.cpp:1182
locale_t value
Definition data_table.cpp:49
presentation::styled_text text
Definition display.cpp:141
Definition qnum.hpp:403
std::string trim(const std::string &s)
Definition terminal.cpp:162
parse_result parse(CLI::App &app, int argc, char const *const *argv, parse_policy policy)
Parse argc/argv, preserving CLI11's argument boundaries and help precedence.
Definition cli.cpp:116
auto pack(Span const &span, bool read=true)
Definition binary80_common.hpp:23
void unpack(Tensor const &source, Span &target)
Definition packed_matrix.hpp:33
void sum(BackendSelector selector, async::Async< OutputTensor > &output, async::Async< InputTensor > const &input)
Schedule a full sum into an explicit async scalar tensor.
Definition linalg/async/reductions.hpp:309
auto to_string(basic_half_int< T > value) -> std::string
Format a half-integer as an integer or decimal .5.
Definition half_int.hpp:477
std::size_t span
Definition presentation.cpp:2017
Defines U1, the value-level quantum number type for one U(1) symmetry factor.