cppqed-multidiagonal 0.4.0
Multidiagonal sparse operator algebra for C++QED
Loading...
Searching...
No Matches
MultiDiagonal.h
Go to the documentation of this file.
1// Copyright András Vukics 2006–2026. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE.txt)
2#pragma once
3
4#include "MultiArrayComplex.h"
5
6#include <algorithm>
7#include <map>
8
9
11namespace quantumoperator {
12
13using namespace cppqedutils;
14
15
56template <size_t RANK>
58{
62 using Offsets = std::array<ptrdiff_t,RANK>;
64
66 using Diagonals = std::map<Offsets,Diagonal>;
67
70
73 explicit MultiDiagonal(auto&&... args) requires std::constructible_from<Diagonals,decltype(args)...> : diagonals{FWD(args)...} {}
74
77 {
79 for (const auto& [offsets,diag] : md.diagonals) res.diagonals.emplace(offsets,copy(diag));
80 return res;
81 }
82
84
99 {
100 if (diagonals.empty()) return;
101
102#ifndef NDEBUG
103 if (psi.extents != calculateAndCheckDimensions(*this)) throw std::runtime_error("Mismatch between StateVector and MultiDiagonal dimensions");
104 checkExtents(psi,MultiArrayConstView<dcomp,RANK>{dpsidt},"MultiDiagonal application");
105#endif // NDEBUG
106
107 const size_t strideRows0{dpsidt.strides[0]}, strideCols0{psi.strides[0]};
108
109 for (const auto& [offsets,diag] : diagonals) {
110
111 if (diag.dataView.empty()) continue; // |o|=D on some axis: a structural zero
112
113 // first row o⁻ in dpsidt, first column o⁺ in psi
115 for (auto&& [o,sR,sC] : std::views::zip(offsets,dpsidt.strides,psi.strides)) {
116 if (o<0) rows+=sR*size_t(-o); else cols+=sC*size_t(o);
117 }
118
119 // The loop nest walks the diagonal d in storage order (an owning MultiArray, contiguous, axis 0 fastest), and returns
120 // d advanced past the entries it used. len0 is the length of axis 0 if fixed at compile time, otherwise 0.
121 // Bounds and strides are copied, and d is passed by value, so that the compiler sees that the stores into dpsidt
122 // don't change them, also where it doesn't inline the lambda; otherwise it cannot vectorize.
123 const auto loop=[&] (auto n, const auto& self, auto len0, dcomp* r, const dcomp* c, const dcomp* d) -> const dcomp*
124 {
125 using namespace hana::literals;
126 if constexpr (n!=0_c) {
127 const size_t extent{diag.extents[n]}, sR{dpsidt.strides[n]}, sC{psi.strides[n]};
128 for (size_t k=0; k<extent; ++k) d=self(n-1_c,self,len0,r+k*sR,c+k*sC,d);
129 return d;
130 }
131 else {
132 const size_t sR{strideRows0}, sC{strideCols0};
133 const auto multiplyAdd=[=] (size_t k) {r[k*sR]+=textbookProduct(d[k],c[k*sC]);};
134 if constexpr (decltype(len0)::value) {
135 hana::for_each(hana::make_range(hana::size_c<0>,len0),multiplyAdd);
136 return d+decltype(len0)::value;
137 }
138 else {
139 const size_t len{diag.extents[0]};
140 if (sR==1 && sC==1) for (size_t k=0; k<len; ++k) r[k]+=textbookProduct(d[k],c[k]);
141 else for (size_t k=0; k<len; ++k) multiplyAdd(k);
142 return d+len;
143 }
144 }
145 };
146
147 const auto run=[&] (auto len0) {loop(hana::llong_c<RANK-1>,loop,len0,rows,cols,diag.dataView.data());};
148
149 // A short axis 0 is unrolled at compile time: a vectorized loop over a few elements costs more than it saves.
150 // Up to four is where the two cross over (benchmarks/kernels.cc, case E).
151 switch (diag.extents[0]) {
152 case 1: run(hana::size_c<1>); break;
153 case 2: run(hana::size_c<2>); break;
154 case 3: run(hana::size_c<3>); break;
155 case 4: run(hana::size_c<4>); break;
156 default: run(hana::size_c<0>);
157 }
158 }
159 }
160
161
164 template <size_t RANK2>
166 {
168 for (const auto& [offsets1,diag1] : md1.diagonals) for (const auto& [offsets2,diag2] : md2.diagonals)
170 return res;
171 }
172
173
175
176
180 {
182 for (auto& [offsets,diag] : diagonals) {
184 for (size_t i=0; i<RANK; ++i) newOffsets[i]=-offsets[i];
185 conj(diag);
186 newDiagonals.emplace(newOffsets,std::move(diag));
187 }
189 return *this;
190 }
191
193
194 friend MultiDiagonal hermitianConjugateOf(const MultiDiagonal& md) {MultiDiagonal res(copy(md)); res.hermitianConjugate(); return res;}
195
198
204
205
208
209 MultiDiagonal operator-() const {MultiDiagonal res{copy(*this)}; res*=-1.; return res;}
210
211 MultiDiagonal operator+() const {return copy(*this);}
212
215 {
216#ifndef NDEBUG
218 dim!=Dimensions{} && mdDim!=Dimensions{} && dim!=mdDim)
219 throw std::runtime_error("Dimension mismatch in addition of MultiDiagonals");
220#endif // NDEBUG
221 for (const auto& [offsets,diag] : md.diagonals)
222 if (auto it=diagonals.find(offsets); it!=diagonals.end())
223 for (auto&& [to,from] : std::views::zip(it->second.dataStorage(),diag.dataStorage())) to+=from;
224 else diagonals.emplace(offsets,copy(diag));
225 return *this;
226 }
227
229
231 {
232 for (auto& [offsets,diag] : diagonals) for (dcomp& v : diag.dataStorage()) v*=d;
233 return *this;
234 }
235
236 MultiDiagonal& operator/=(scalar auto d) {(*this)*=1./d; return *this;}
237
238 friend auto operator+(const MultiDiagonal& md1, const MultiDiagonal& md2) {MultiDiagonal res{copy(md1)}; res+=md2; return res;}
239 friend auto operator-(const MultiDiagonal& md1, const MultiDiagonal& md2) {MultiDiagonal res{copy(md1)}; res-=md2; return res;}
240
241 friend auto operator*(scalar auto v, const MultiDiagonal& md) {MultiDiagonal res{copy(md)}; res*=v; return res;}
242 friend auto operator/(const MultiDiagonal& md, scalar auto v) {MultiDiagonal res{copy(md)}; res/=v; return res;}
244
245
250 {
251 if (md.diagonals.empty()) return {};
252
253 auto impliedDimensions = [] (const auto& kv) -> Dimensions {
254 auto res{kv.second.extents};
255 for (auto&& [v,o] : std::views::zip(res,kv.first)) v += size_t(o<0 ? -o : o);
256 return res;
257 };
258
259 const auto res{impliedDimensions(*md.diagonals.cbegin())};
260
261 for (const auto& kv : md.diagonals)
262 if (impliedDimensions(kv)!=res) throw std::runtime_error("Dimensions mismatch in MultiDiagonal");
263
264 return res;
265 }
266
268 friend void tag_invoke( const json::value_from_tag&, json::value& jv, const MultiDiagonal& md )
269 {
270 json::array& arr = jv.emplace_array();
271 for (const auto& [offsets,diag] : md.diagonals)
272 arr.push_back( json::object{ {"offsets",json::value_from(offsets)}, {"diagonal",json::value_from(diag)} } );
273 }
274
275};
276
277
297template <size_t RANK>
299{
300 using MD = MultiDiagonal<RANK>;
301
302 if (a.diagonals.empty() || b.diagonals.empty()) return MD{}; // composition with the zero operator
303
304 const auto dim = calculateAndCheckDimensions(a);
305 if (dim != calculateAndCheckDimensions(b)) throw std::runtime_error("Mismatch in MultiDiagonal composition dimensions");
306
307 MD res;
308
309 for (const auto& [p,diagA] : a.diagonals) for (const auto& [q,diagB] : b.diagonals) {
310
311 typename MD::Offsets s; // s = p + q
312 Extents<RANK> resExtents, start, box; // result-diagonal extents; valid window [start, start+box)
313 std::array<ptrdiff_t,RANK> iA, iB; // index shifts into the operand diagonals
314
315 bool nonzero=true;
316
317 for (size_t i=0; i<RANK; ++i) {
318 const ptrdiff_t D=ptrdiff_t(dim[i]), pi=p[i], qi=q[i], si=pi+qi,
319 sm = si<0 ? -si : 0, pm = pi<0 ? -pi : 0, qm = qi<0 ? -qi : 0, // negative parts o⁻
320 sa = si<0 ? -si : si, pa = pi<0 ? -pi : pi, qa = qi<0 ? -qi : qi; // magnitudes |o|
321
322 if (sa >= D) {nonzero=false; break;}
323
324 const ptrdiff_t ia = sm - pm, ib = sm + pi - qm,
325 st = std::max({ptrdiff_t{0}, -ia, -ib}),
326 en = std::min({D - sa, D - pa - ia, D - qa - ib});
327
328 if (en <= st) {nonzero=false; break;}
329
330 s[i]=si; resExtents[i]=size_t(D-sa); start[i]=size_t(st); box[i]=size_t(en-st); iA[i]=ia; iB[i]=ib;
331 }
332 if (!nonzero) continue;
333
334 // find-or-create the result diagonal (zero-initialized); accumulate the windowed products
335 auto& resDiag = res.diagonals.try_emplace(s, resExtents, multiarray::zeroInit<dcomp>).first->second;
336
337 for (Extents<RANK> t{}; t[0]!=box[0]; incrementMultiIndex(t,box)) {
339 for (size_t i=0; i<RANK; ++i) {
340 kRes[i]=start[i]+t[i];
341 kA[i]=size_t(ptrdiff_t(kRes[i])+iA[i]);
342 kB[i]=size_t(ptrdiff_t(kRes[i])+iB[i]);
343 }
344 resDiag(kRes) += diagA(kA)*diagB(kB);
345 }
346 }
347
348 return res;
349}
350
351
352namespace multidiagonal {
353
356
357} // multidiagonal
358
359
360} // quantumoperator
361
362
364
std::complex< double > dcomp
Double-precision complex number. Named to resemble a built-in type rather than a class.
Definition MultiArrayComplex.h:13
dcomp textbookProduct(dcomp a, dcomp b)
Complex product by the textbook formula , for hot loops.
Definition MultiArrayComplex.h:56
#define FWD(x)
Perfect-forwarding macro. Expands to std::forward<decltype(x)>(x), avoiding the need to repeat the ty...
Definition Traits.h:5
A non-owning, strided view over a contiguous buffer. Should be passed by value.
Definition MultiArray.h:106
Extents< RANK > extents
Definition MultiArray.h:172
size_t offset
Definition MultiArray.h:174
std::span< T > dataView
Definition MultiArray.h:176
Extents< RANK > strides
Definition MultiArray.h:172
std::vector< T > StorageType
Underlying storage type: std::vector<T>. May eventually be replaced by kokkos::View or std::valarray.
Definition MultiArray.h:259
Namespace alias for Boost.Hana, used for compile-time sequences and heterogeneous containers.
Definition MultiArray.h:17
constexpr auto concatenate(const std::array< Type, sizes > &... arrays)
Concatenates multiple std::array instances of different sizes into one.
Definition MultiArray.h:33
MultiArray< dcomp, RANK > & conj(MultiArray< dcomp, RANK > &ma)
In-place complex conjugation of all elements of ma.
Definition MultiArrayComplex.h:155
auto & incrementMultiIndex(Extents< RANK > &idx, Extents< RANK > extents)
Increments a multi-index in last-index-fastest order, in-place.
Definition MultiArray.h:61
void checkExtents(MultiArrayConstView< T1, RANK > m1, MultiArrayConstView< T2, RANK > m2, std::string message)
Debug-mode extent check between two views. Throws std::runtime_error on mismatch.
Definition MultiArray.h:201
auto directProduct(const MultiArray< dcomp, RANK1 > &m1, const MultiArray< dcomp, RANK2 > &m2, std::function< dcomp(dcomp, dcomp)> func=std::multiplies< dcomp >{})
Tensor (outer) product of two complex multi-arrays.
Definition MultiArrayComplex.h:144
MultiDiagonal< 1 > identity(size_t dim)
Constructs the rank-1 identity operator of dimension dim.
Definition MultiDiagonal.h:365
Comprises modules representing operators of special structure (multidiagonal, sparse) over Hilbert sp...
Definition MultiDiagonal.h:11
MultiDiagonal< RANK > operator|(const MultiDiagonal< RANK > &a, const MultiDiagonal< RANK > &b)
Composition (matrix product) of two MultiDiagonal operators of arbitrary rank: .
Definition MultiDiagonal.h:298
A sparse matrix storing an arbitrary set of diagonals, closed under composition, direct product,...
Definition MultiDiagonal.h:58
MultiDiagonal & dagger()
Definition MultiDiagonal.h:192
friend MultiDiagonal twoITimesImagPartOf(const MultiDiagonal &md)
, which is anti-Hermitian.
Definition MultiDiagonal.h:202
MultiDiagonal & operator+=(const MultiDiagonal &md)
In-place addition. New diagonals are inserted; existing ones accumulate element-wise.
Definition MultiDiagonal.h:214
friend void tag_invoke(const json::value_from_tag &, json::value &jv, const MultiDiagonal &md)
Boost.JSON serialization: emits an array of {"offsets": [...], "diagonal": {...}} objects.
Definition MultiDiagonal.h:268
MultiDiagonal & operator*=(scalar auto d)
Definition MultiDiagonal.h:230
MultiDiagonal operator-() const
Definition MultiDiagonal.h:209
friend MultiDiagonal twoTimesRealPartOf(const MultiDiagonal &md)
, which is Hermitian.
Definition MultiDiagonal.h:197
void operator()(double, MultiArrayConstView< dcomp, RANK > psi, MultiArrayView< dcomp, RANK > dpsidt) const
Accumulates the operator's action on psi into dpsidt: dpsidt(k+o⁻) += diag(k) * psi(k+o⁺) for each di...
Definition MultiDiagonal.h:98
std::map< Offsets, Diagonal > Diagonals
Single-level map: signed offsets → diagonal data.
Definition MultiDiagonal.h:66
friend auto operator-(const MultiDiagonal &md1, const MultiDiagonal &md2)
Definition MultiDiagonal.h:239
friend Dimensions calculateAndCheckDimensions(const MultiDiagonal &md)
Derives and validates the Hilbert space dimensions from the invariant . Returns Dimensions{} for an e...
Definition MultiDiagonal.h:249
friend auto operator+(const MultiDiagonal &md1, const MultiDiagonal &md2)
Definition MultiDiagonal.h:238
friend auto operator*(scalar auto v, const MultiDiagonal &md)
Definition MultiDiagonal.h:241
MultiDiagonal & operator=(const MultiDiagonal &)=delete
Diagonals diagonals
Definition MultiDiagonal.h:83
friend auto operator/(const MultiDiagonal &md, scalar auto v)
Definition MultiDiagonal.h:242
MultiDiagonal(auto &&... args)
Forwards args to the diagonal map. Constrained, so that a non-const lvalue MultiDiagonal reaches the ...
Definition MultiDiagonal.h:73
MultiDiagonal & operator/=(scalar auto d)
Definition MultiDiagonal.h:236
MultiDiagonal & operator-=(const MultiDiagonal &md)
Definition MultiDiagonal.h:228
friend MultiDiagonal hermitianConjugateOf(const MultiDiagonal &md)
Definition MultiDiagonal.h:194
friend auto operator*(const MultiDiagonal< RANK > &md1, const MultiDiagonal< RANK2 > &md2)
Tensor (direct) product: concatenates the signed offset arrays, reflecting the tensor product structu...
Definition MultiDiagonal.h:165
MultiDiagonal & hermitianConjugate()
In-place Hermitian conjugation: negates all offsets and conjugates all diagonal elements....
Definition MultiDiagonal.h:179
std::array< ptrdiff_t, RANK > Offsets
Signed offsets, one per axis: positive = upper, negative = lower, zero = main diagonal.
Definition MultiDiagonal.h:62
MultiDiagonal(MultiDiagonal &&)=default
MultiDiagonal operator+() const
Definition MultiDiagonal.h:211
friend MultiDiagonal copy(const MultiDiagonal &md)
Named deep copy.
Definition MultiDiagonal.h:76
MultiDiagonal(const MultiDiagonal &)=delete