1 /*
2 * This file is part of Quantum++.
3 *
4 * Copyright (c) 2013 - 2021 softwareQ Inc. All rights reserved.
5 *
6 * MIT License
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 * SOFTWARE.
25 */
26
27 /**
28 * \file functions.hpp
29 * \brief Generic quantum computing functions
30 */
31
32 #ifndef FUNCTIONS_HPP_
33 #define FUNCTIONS_HPP_
34
35 namespace qpp {
36 // Eigen function wrappers
37 /**
38 * \brief Transpose
39 *
40 * \param A Eigen expression
41 * \return Transpose of \a A, as a dynamic matrix over the same scalar field as
42 * \a A
43 */
44 template <typename Derived>
45 dyn_mat<typename Derived::Scalar>
transpose(const Eigen::MatrixBase<Derived> & A)46 transpose(const Eigen::MatrixBase<Derived>& A) {
47 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
48
49 // EXCEPTION CHECKS
50
51 // check zero-size
52 if (!internal::check_nonzero_size(rA))
53 throw exception::ZeroSize("qpp::transpose()");
54 // END EXCEPTION CHECKS
55
56 return rA.transpose();
57 }
58
59 /**
60 * \brief Complex conjugate
61 *
62 * \param A Eigen expression
63 * \return Complex conjugate of \a A, as a dynamic matrix over the same scalar
64 * field as \a A
65 */
66 template <typename Derived>
67 dyn_mat<typename Derived::Scalar>
conjugate(const Eigen::MatrixBase<Derived> & A)68 conjugate(const Eigen::MatrixBase<Derived>& A) {
69 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
70
71 // EXCEPTION CHECKS
72
73 // check zero-size
74 if (!internal::check_nonzero_size(rA))
75 throw exception::ZeroSize("qpp::conjugate()");
76 // END EXCEPTION CHECKS
77
78 return rA.conjugate();
79 }
80
81 /**
82 * \brief Adjoint
83 *
84 * \param A Eigen expression
85 * \return Adjoint (Hermitian conjugate) of \a A, as a dynamic matrix over the
86 * same scalar field as \a A
87 */
88 template <typename Derived>
adjoint(const Eigen::MatrixBase<Derived> & A)89 dyn_mat<typename Derived::Scalar> adjoint(const Eigen::MatrixBase<Derived>& A) {
90 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
91
92 // EXCEPTION CHECKS
93
94 // check zero-size
95 if (!internal::check_nonzero_size(rA))
96 throw exception::ZeroSize("qpp::adjoint()");
97 // END EXCEPTION CHECKS
98
99 return rA.adjoint();
100 }
101
102 /**
103 * \brief Inverse
104 *
105 * \param A Eigen expression
106 * \return Inverse of \a A, as a dynamic matrix over the same scalar field as
107 * \a A
108 */
109 template <typename Derived>
inverse(const Eigen::MatrixBase<Derived> & A)110 dyn_mat<typename Derived::Scalar> inverse(const Eigen::MatrixBase<Derived>& A) {
111 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
112
113 // EXCEPTION CHECKS
114
115 // check zero-size
116 if (!internal::check_nonzero_size(rA))
117 throw exception::ZeroSize("qpp::inverse()");
118
119 // check square matrix
120 if (!internal::check_square_mat(rA))
121 throw exception::MatrixNotSquare("qpp::inverse()");
122 // END EXCEPTION CHECKS
123
124 return rA.inverse();
125 }
126
127 /**
128 * \brief Trace
129 *
130 * \param A Eigen expression
131 * \return Trace of \a A, as a scalar over the same scalar field as \a A
132 */
133 template <typename Derived>
trace(const Eigen::MatrixBase<Derived> & A)134 typename Derived::Scalar trace(const Eigen::MatrixBase<Derived>& A) {
135 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
136
137 // EXCEPTION CHECKS
138
139 // check zero-size
140 if (!internal::check_nonzero_size(rA))
141 throw exception::ZeroSize("qpp::trace()");
142
143 // check square matrix
144 if (!internal::check_square_mat(rA))
145 throw exception::MatrixNotSquare("qpp::trace()");
146 // END EXCEPTION CHECKS
147
148 return rA.trace();
149 }
150
151 /**
152 * \brief Determinant
153 *
154 * \param A Eigen expression
155 * \return Determinant of \a A, as a scalar over the same scalar field as \a A.
156 * Returns \f$\pm \infty\f$ when the determinant overflows/underflows.
157 */
158 template <typename Derived>
det(const Eigen::MatrixBase<Derived> & A)159 typename Derived::Scalar det(const Eigen::MatrixBase<Derived>& A) {
160 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
161
162 // EXCEPTION CHECKS
163
164 // check zero-size
165 if (!internal::check_nonzero_size(rA))
166 throw exception::ZeroSize("qpp::det()");
167
168 // check square matrix
169 if (!internal::check_square_mat(rA))
170 throw exception::MatrixNotSquare("qpp::det()");
171 // END EXCEPTION CHECKS
172
173 return rA.determinant();
174 }
175
176 /**
177 * \brief Logarithm of the determinant
178 *
179 * Useful when the determinant overflows/underflows
180 *
181 * \param A Eigen expression
182 * \return Logarithm of the determinant of \a A, as a scalar over the same
183 * scalar field as \a A
184 */
185 template <typename Derived>
logdet(const Eigen::MatrixBase<Derived> & A)186 typename Derived::Scalar logdet(const Eigen::MatrixBase<Derived>& A) {
187 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
188
189 // EXCEPTION CHECKS
190
191 // check zero-size
192 if (!internal::check_nonzero_size(rA))
193 throw exception::ZeroSize("qpp::logdet()");
194
195 // check square matrix
196 if (!internal::check_square_mat(rA))
197 throw exception::MatrixNotSquare("qpp::logdet()");
198 // END EXCEPTION CHECKS
199
200 Eigen::PartialPivLU<dyn_mat<typename Derived::Scalar>> lu(rA);
201 dyn_mat<typename Derived::Scalar> U =
202 lu.matrixLU().template triangularView<Eigen::Upper>();
203 typename Derived::Scalar result = std::log(U(0, 0));
204
205 for (idx i = 1; i < static_cast<idx>(rA.rows()); ++i)
206 result += std::log(U(i, i));
207
208 return result;
209 }
210
211 /**
212 * \brief Element-wise sum of \a A
213 *
214 * \param A Eigen expression
215 * \return Element-wise sum of \a A, as a scalar over the same scalar field as
216 * \a A
217 */
218 template <typename Derived>
sum(const Eigen::MatrixBase<Derived> & A)219 typename Derived::Scalar sum(const Eigen::MatrixBase<Derived>& A) {
220 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
221
222 // EXCEPTION CHECKS
223
224 // check zero-size
225 if (!internal::check_nonzero_size(rA))
226 throw exception::ZeroSize("qpp::sum()");
227 // END EXCEPTION CHECKS
228
229 return rA.sum();
230 }
231
232 /**
233 * \brief Element-wise product of \a A
234 *
235 * \param A Eigen expression
236 * \return Element-wise product of \a A, as a scalar over the same scalar field
237 * as \a A
238 */
239 template <typename Derived>
prod(const Eigen::MatrixBase<Derived> & A)240 typename Derived::Scalar prod(const Eigen::MatrixBase<Derived>& A) {
241 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
242
243 // EXCEPTION CHECKS
244
245 // check zero-size
246 if (!internal::check_nonzero_size(rA))
247 throw exception::ZeroSize("qpp::prod()");
248 // END EXCEPTION CHECKS
249
250 return rA.prod();
251 }
252
253 /**
254 * \brief Frobenius norm
255 *
256 * \param A Eigen expression
257 * \return Frobenius norm of \a A
258 */
259 template <typename Derived>
norm(const Eigen::MatrixBase<Derived> & A)260 double norm(const Eigen::MatrixBase<Derived>& A) {
261 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
262
263 // EXCEPTION CHECKS
264
265 // check zero-size
266 if (!internal::check_nonzero_size(rA))
267 throw exception::ZeroSize("qpp::norm()");
268 // END EXCEPTION CHECKS
269
270 // convert matrix to complex then return its norm
271 return (rA.template cast<cplx>()).norm();
272 }
273
274 /**
275 * \brief Normalizes state vector (column or row vector) or density matrix
276 *
277 * \param A Eigen expression
278 * \return Normalized state vector or density matrix
279 */
280 template <typename Derived>
281 dyn_mat<typename Derived::Scalar>
normalize(const Eigen::MatrixBase<Derived> & A)282 normalize(const Eigen::MatrixBase<Derived>& A) {
283 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
284
285 // EXCEPTION CHECKS
286
287 // check zero size
288 if (!internal::check_nonzero_size(rA))
289 throw exception::ZeroSize("qpp::normalize()");
290 // END EXCEPTION CHECKS
291
292 dyn_mat<typename Derived::Scalar> result;
293
294 if (internal::check_cvector(rA) || internal::check_rvector(rA)) {
295 double normA = norm(rA);
296 if (normA == 0) {
297 throw std::overflow_error("qpp::normalize(): Division by zero!");
298 }
299 result = rA / normA;
300 } else if (internal::check_square_mat(rA)) {
301 typename Derived::Scalar traceA = trace(rA);
302 if (std::abs(traceA) == 0) {
303 throw std::overflow_error("qpp::normalize(): Division by zero!");
304 }
305 result = rA / trace(rA);
306 } else
307 throw exception::MatrixNotSquareNorVector("qpp::normalize()");
308
309 return result;
310 }
311
312 /**
313 * \brief Full eigen decomposition
314 * \see qpp::heig()
315 *
316 * \param A Eigen expression
317 * \return Pair of: 1. Eigenvalues of \a A, as a complex dynamic column vector,
318 * and 2. Eigenvectors of \a A, as columns of a complex dynamic matrix
319 */
320 template <typename Derived>
eig(const Eigen::MatrixBase<Derived> & A)321 std::pair<dyn_col_vect<cplx>, cmat> eig(const Eigen::MatrixBase<Derived>& A) {
322 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
323
324 // EXCEPTION CHECKS
325
326 // check zero-size
327 if (!internal::check_nonzero_size(rA))
328 throw exception::ZeroSize("qpp::eig()");
329
330 // check square matrix
331 if (!internal::check_square_mat(rA))
332 throw exception::MatrixNotSquare("qpp::eig()");
333 // END EXCEPTION CHECKS
334
335 Eigen::ComplexEigenSolver<cmat> es(rA.template cast<cplx>());
336
337 return std::make_pair(es.eigenvalues(), es.eigenvectors());
338 }
339
340 /**
341 * \brief Eigenvalues
342 * \see qpp::hevals()
343 *
344 * \param A Eigen expression
345 * \return Eigenvalues of \a A, as a complex dynamic column vector
346 */
347 template <typename Derived>
evals(const Eigen::MatrixBase<Derived> & A)348 dyn_col_vect<cplx> evals(const Eigen::MatrixBase<Derived>& A) {
349 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
350
351 // EXCEPTION CHECKS
352
353 // check zero-size
354 if (!internal::check_nonzero_size(rA))
355 throw exception::ZeroSize("qpp::evals()");
356
357 // check square matrix
358 if (!internal::check_square_mat(rA))
359 throw exception::MatrixNotSquare("qpp::evals()");
360 // END EXCEPTION CHECKS
361
362 return eig(rA).first;
363 }
364
365 /**
366 * \brief Eigenvectors
367 * \see qpp::hevects()
368 *
369 * \param A Eigen expression
370 * \return Eigenvectors of \a A, as columns of a complex dynamic matrix
371 */
372 template <typename Derived>
evects(const Eigen::MatrixBase<Derived> & A)373 cmat evects(const Eigen::MatrixBase<Derived>& A) {
374 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
375
376 // EXCEPTION CHECKS
377
378 // check zero-size
379 if (!internal::check_nonzero_size(rA))
380 throw exception::ZeroSize("qpp::evects()");
381
382 // check square matrix
383 if (!internal::check_square_mat(rA))
384 throw exception::MatrixNotSquare("qpp::evects()");
385 // END EXCEPTION CHECKS
386
387 Eigen::ComplexEigenSolver<cmat> es(rA.template cast<cplx>());
388
389 return eig(rA).second;
390 }
391
392 /**
393 * \brief Full eigen decomposition of Hermitian expression
394 * \see qpp::eig()
395 *
396 * \param A Eigen expression, assumed to be Hermitian
397 * \return Pair of: 1. Eigenvalues of \a A, as a real dynamic column vector,
398 * and 2. Eigenvectors of \a A, as columns of a complex dynamic matrix
399 */
400 template <typename Derived>
401 std::pair<dyn_col_vect<double>, cmat>
heig(const Eigen::MatrixBase<Derived> & A)402 heig(const Eigen::MatrixBase<Derived>& A) {
403 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
404
405 // EXCEPTION CHECKS
406
407 // check zero-size
408 if (!internal::check_nonzero_size(rA))
409 throw exception::ZeroSize("qpp::heig()");
410
411 // check square matrix
412 if (!internal::check_square_mat(rA))
413 throw exception::MatrixNotSquare("qpp::heig()");
414 // END EXCEPTION CHECKS
415
416 Eigen::SelfAdjointEigenSolver<cmat> es(rA.template cast<cplx>());
417
418 return std::make_pair(es.eigenvalues(), es.eigenvectors());
419 }
420
421 /**
422 * \brief Hermitian eigenvalues
423 * \see qpp::evals()
424 *
425 * \param A Eigen expression, assumed to be Hermitian
426 * \return Eigenvalues of Hermitian \a A, as a real dynamic column vector
427 */
428 template <typename Derived>
hevals(const Eigen::MatrixBase<Derived> & A)429 dyn_col_vect<double> hevals(const Eigen::MatrixBase<Derived>& A) {
430 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
431
432 // EXCEPTION CHECKS
433
434 // check zero-size
435 if (!internal::check_nonzero_size(rA))
436 throw exception::ZeroSize("qpp::hevals()");
437
438 // check square matrix
439 if (!internal::check_square_mat(rA))
440 throw exception::MatrixNotSquare("qpp::hevals()");
441 // END EXCEPTION CHECKS
442
443 return heig(rA).first;
444 }
445
446 /**
447 * \brief Eigenvectors of Hermitian matrix
448 * \see qpp::evects()
449 *
450 * \param A Eigen expression, assumed to be Hermitian
451 * \return Eigenvectors of Hermitian matrix \a A, as columns of a complex matrix
452 */
453 template <typename Derived>
hevects(const Eigen::MatrixBase<Derived> & A)454 cmat hevects(const Eigen::MatrixBase<Derived>& A) {
455 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
456
457 // EXCEPTION CHECKS
458
459 // check zero-size
460 if (!internal::check_nonzero_size(rA))
461 throw exception::ZeroSize("qpp::hevects()");
462
463 // check square matrix
464 if (!internal::check_square_mat(rA))
465 throw exception::MatrixNotSquare("qpp::hevects()");
466 // END EXCEPTION CHECKS
467
468 return heig(rA).second;
469 }
470
471 /**
472 * \brief Full singular value decomposition
473 *
474 * \param A Eigen expression
475 * \return Tuple of: 1. Left sigular vectors of \a A, as columns of a complex
476 * dynamic matrix, 2. Singular values of \a A, ordered in decreasing order,
477 * as a real dynamic column vector, and 3. Right singular vectors of \a A,
478 * as columns of a complex dynamic matrix
479 */
480 template <typename Derived>
481 std::tuple<cmat, dyn_col_vect<double>, cmat>
svd(const Eigen::MatrixBase<Derived> & A)482 svd(const Eigen::MatrixBase<Derived>& A) {
483 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
484
485 // EXCEPTION CHECKS
486
487 // check zero-size
488 if (!internal::check_nonzero_size(rA))
489 throw exception::ZeroSize("qpp::svd()");
490 // END EXCEPTION CHECKS
491
492 auto const sv = rA.bdcSvd(Eigen::ComputeFullU | Eigen::ComputeFullV);
493
494 return std::make_tuple(sv.matrixU(), sv.singularValues(), sv.matrixV());
495 }
496
497 /**
498 * \brief Singular values
499 *
500 * \param A Eigen expression
501 * \return Singular values of \a A, ordered in decreasing order, as a real
502 * dynamic column vector
503 */
504 template <typename Derived>
svals(const Eigen::MatrixBase<Derived> & A)505 dyn_col_vect<double> svals(const Eigen::MatrixBase<Derived>& A) {
506 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
507
508 // EXCEPTION CHECKS
509
510 // check zero-size
511 if (!internal::check_nonzero_size(rA))
512 throw exception::ZeroSize("qpp::svals()");
513 // END EXCEPTION CHECKS
514
515 return rA.bdcSvd().singularValues();
516 }
517
518 /**
519 * \brief Left singular vectors
520 *
521 * \param A Eigen expression
522 * \return Complex dynamic matrix, whose columns are the left singular vectors
523 * of \a A
524 */
525 template <typename Derived>
svdU(const Eigen::MatrixBase<Derived> & A)526 cmat svdU(const Eigen::MatrixBase<Derived>& A) {
527 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
528
529 // EXCEPTION CHECKS
530
531 // check zero-size
532 if (!internal::check_nonzero_size(rA))
533 throw exception::ZeroSize("qpp::svdU()");
534 // END EXCEPTION CHECKS
535
536 return rA.bdcSvd(Eigen::ComputeFullU).matrixU();
537 }
538
539 /**
540 * \brief Right singular vectors
541 *
542 * \param A Eigen expression
543 * \return Complex dynamic matrix, whose columns are the right singular vectors
544 * of \a A
545 */
546 template <typename Derived>
svdV(const Eigen::MatrixBase<Derived> & A)547 cmat svdV(const Eigen::MatrixBase<Derived>& A) {
548 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
549
550 // EXCEPTION CHECKS
551
552 // check zero-size
553 if (!internal::check_nonzero_size(rA))
554 throw exception::ZeroSize("qpp::svdV()");
555 // END EXCEPTION CHECKS
556
557 return rA.bdcSvd(Eigen::ComputeFullV).matrixV();
558 }
559
560 // Matrix functional calculus
561
562 /**
563 * \brief Functional calculus f(A)
564 *
565 * \note Does not take into account issues such as numerical stability etc.
566 *
567 * \param A Diagonalizable Eigen expression
568 * \param f Pointer-to-function from complex to complex
569 * \return \a \f$f(A)\f$
570 */
571 template <typename Derived>
funm(const Eigen::MatrixBase<Derived> & A,cplx (* f)(const cplx &))572 cmat funm(const Eigen::MatrixBase<Derived>& A, cplx (*f)(const cplx&)) {
573 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
574
575 // EXCEPTION CHECKS
576
577 // check zero-size
578 if (!internal::check_nonzero_size(rA))
579 throw exception::ZeroSize("qpp::funm()");
580
581 // check square matrix
582 if (!internal::check_square_mat(rA))
583 throw exception::MatrixNotSquare("qpp::funm()");
584 // END EXCEPTION CHECKS
585
586 Eigen::ComplexEigenSolver<cmat> es(rA.template cast<cplx>());
587 const cmat& evects = es.eigenvectors();
588 cmat evals = es.eigenvalues();
589 for (idx i = 0; i < static_cast<idx>(evals.rows()); ++i)
590 evals(i) = (*f)(evals(i)); // apply f(x) to each eigenvalue
591
592 cmat evalsdiag = evals.asDiagonal();
593
594 return evects * evalsdiag * evects.inverse();
595 }
596
597 /**
598 * \brief Matrix square root
599 *
600 * \param A Eigen expression
601 * \return Matrix square root of \a A
602 */
603 template <typename Derived>
sqrtm(const Eigen::MatrixBase<Derived> & A)604 cmat sqrtm(const Eigen::MatrixBase<Derived>& A) {
605 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
606
607 // EXCEPTION CHECKS
608
609 // check zero-size
610 if (!internal::check_nonzero_size(rA))
611 throw exception::ZeroSize("qpp::sqrtm()");
612
613 // check square matrix
614 if (!internal::check_square_mat(rA))
615 throw exception::MatrixNotSquare("qpp::sqrtm()");
616 // END EXCEPTION CHECKS
617
618 return funm(rA, &std::sqrt);
619 }
620
621 /**
622 * \brief Matrix absolute value
623 *
624 * \param A Eigen expression
625 * \return Matrix absolute value of \a A
626 */
627 template <typename Derived>
absm(const Eigen::MatrixBase<Derived> & A)628 cmat absm(const Eigen::MatrixBase<Derived>& A) {
629 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
630
631 // EXCEPTION CHECKS
632
633 // check zero-size
634 if (!internal::check_nonzero_size(rA))
635 throw exception::ZeroSize("qpp::absm()");
636
637 // check square matrix
638 if (!internal::check_square_mat(rA))
639 throw exception::MatrixNotSquare("qpp::absm()");
640 // END EXCEPTION CHECKS
641
642 return sqrtm(adjoint(rA) * rA);
643 }
644
645 /**
646 * \brief Matrix exponential
647 *
648 * \param A Eigen expression
649 * \return Matrix exponential of \a A
650 */
651 template <typename Derived>
expm(const Eigen::MatrixBase<Derived> & A)652 cmat expm(const Eigen::MatrixBase<Derived>& A) {
653 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
654
655 // EXCEPTION CHECKS
656
657 // check zero-size
658 if (!internal::check_nonzero_size(rA))
659 throw exception::ZeroSize("qpp::expm()");
660
661 // check square matrix
662 if (!internal::check_square_mat(rA))
663 throw exception::MatrixNotSquare("qpp::expm()");
664 // END EXCEPTION CHECKS
665
666 return funm(rA, &std::exp);
667 }
668
669 /**
670 * \brief Matrix logarithm
671 *
672 * \param A Eigen expression
673 * \return Matrix logarithm of \a A
674 */
675 template <typename Derived>
logm(const Eigen::MatrixBase<Derived> & A)676 cmat logm(const Eigen::MatrixBase<Derived>& A) {
677 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
678
679 // EXCEPTION CHECKS
680
681 // check zero-size
682 if (!internal::check_nonzero_size(rA))
683 throw exception::ZeroSize("qpp::logm()");
684
685 // check square matrix
686 if (!internal::check_square_mat(rA))
687 throw exception::MatrixNotSquare("qpp::logm()");
688 // END EXCEPTION CHECKS
689
690 return funm(rA, &std::log);
691 }
692
693 /**
694 * \brief Matrix sin
695 *
696 * \param A Eigen expression
697 * \return Matrix sine of \a A
698 */
699 template <typename Derived>
sinm(const Eigen::MatrixBase<Derived> & A)700 cmat sinm(const Eigen::MatrixBase<Derived>& A) {
701 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
702
703 // EXCEPTION CHECKS
704
705 // check zero-size
706 if (!internal::check_nonzero_size(rA))
707 throw exception::ZeroSize("qpp::sinm()");
708
709 // check square matrix
710 if (!internal::check_square_mat(rA))
711 throw exception::MatrixNotSquare("qpp::sinm()");
712 // END EXCEPTION CHECKS
713
714 return funm(rA, &std::sin);
715 }
716
717 /**
718 * \brief Matrix cos
719 *
720 * \param A Eigen expression
721 * \return Matrix cosine of \a A
722 */
723 template <typename Derived>
cosm(const Eigen::MatrixBase<Derived> & A)724 cmat cosm(const Eigen::MatrixBase<Derived>& A) {
725 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
726
727 // EXCEPTION CHECKS
728
729 // check zero-size
730 if (!internal::check_nonzero_size(rA))
731 throw exception::ZeroSize("qpp::cosm()");
732
733 // check square matrix
734 if (!internal::check_square_mat(rA))
735 throw exception::MatrixNotSquare("qpp::cosm()");
736 // END EXCEPTION CHECKS
737
738 return funm(rA, &std::cos);
739 }
740
741 /**
742 * \brief Matrix power
743 * \see qpp::powm()
744 *
745 * Uses the spectral decomposition of \a A to compute the matrix power. By
746 * convention \f$A^0 = I\f$.
747 *
748 * \param A Diagonalizable Eigen expression
749 * \param z Complex number
750 * \return Matrix power \f$A^z\f$
751 */
752 template <typename Derived>
spectralpowm(const Eigen::MatrixBase<Derived> & A,const cplx z)753 cmat spectralpowm(const Eigen::MatrixBase<Derived>& A, const cplx z) {
754 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
755
756 // EXCEPTION CHECKS
757
758 // check zero-size
759 if (!internal::check_nonzero_size(rA))
760 throw exception::ZeroSize("qpp::spectralpowm()");
761
762 // check square matrix
763 if (!internal::check_square_mat(rA))
764 throw exception::MatrixNotSquare("qpp::spectralpowm()");
765 // END EXCEPTION CHECKS
766
767 // Define A^0 = Id, for z IDENTICALLY zero
768 if (real(z) == 0 && imag(z) == 0)
769 return cmat::Identity(rA.rows(), rA.cols());
770
771 Eigen::ComplexEigenSolver<cmat> es(rA.template cast<cplx>());
772 cmat evects = es.eigenvectors();
773 cmat evals = es.eigenvalues();
774 for (idx i = 0; i < static_cast<idx>(evals.rows()); ++i)
775 evals(i) = std::pow(evals(i), z);
776
777 cmat evalsdiag = evals.asDiagonal();
778
779 return evects * evalsdiag * evects.inverse();
780 }
781
782 /**
783 * \brief Fast matrix power based on the SQUARE-AND-MULTIPLY algorithm
784 * \see qpp::spectralpowm()
785 *
786 * Explicitly multiplies the matrix \a A with itself \a n times. By convention
787 * \f$A^0 = I\f$.
788 *
789 * \param A Eigen expression
790 * \param n Non-negative integer
791 * \return Matrix power \f$A^n\f$, as a dynamic matrix over the same scalar
792 * field as \a A
793 */
794 template <typename Derived>
powm(const Eigen::MatrixBase<Derived> & A,idx n)795 dyn_mat<typename Derived::Scalar> powm(const Eigen::MatrixBase<Derived>& A,
796 idx n) {
797 // EXCEPTION CHECKS
798
799 // check zero-size
800 if (!internal::check_nonzero_size(A))
801 throw exception::ZeroSize("qpp::powm()");
802
803 // check square matrix
804 if (!internal::check_square_mat(A))
805 throw exception::MatrixNotSquare("qpp::powm()");
806 // END EXCEPTION CHECKS
807
808 // if n = 1, return the matrix unchanged
809 if (n == 1)
810 return A;
811
812 dyn_mat<typename Derived::Scalar> result =
813 dyn_mat<typename Derived::Scalar>::Identity(A.rows(), A.cols());
814
815 // if n = 0, return the identity (as just prepared in result)
816 if (n == 0)
817 return result;
818
819 dyn_mat<typename Derived::Scalar> cA = A.derived(); // copy
820
821 // fast matrix power
822 for (; n > 0; n /= 2) {
823 if (n % 2)
824 result = (result * cA).eval();
825 cA = (cA * cA).eval();
826 }
827
828 return result;
829 }
830
831 /**
832 * \brief Schatten matrix norm
833 *
834 * \param A Eigen expression
835 * \param p Real number, greater or equal to 1, use qpp::infty for
836 * \f$p = \infty\f$
837 * \return Schatten-\a p matrix norm of \a A
838 */
839 template <typename Derived>
schatten(const Eigen::MatrixBase<Derived> & A,double p)840 double schatten(const Eigen::MatrixBase<Derived>& A, double p) {
841 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
842
843 // EXCEPTION CHECKS
844
845 // check zero-size
846 if (!internal::check_nonzero_size(rA))
847 throw exception::ZeroSize("qpp::schatten()");
848 if (p < 1)
849 throw exception::OutOfRange("qpp::schatten()");
850 // END EXCEPTION CHECKS
851
852 if (p == infty) // infinity norm (largest singular value)
853 return svals(rA)(0);
854
855 const dyn_col_vect<double> sv = svals(rA);
856 double result = 0;
857 for (idx i = 0; i < static_cast<idx>(sv.rows()); ++i)
858 result += std::pow(sv[i], p);
859
860 return std::pow(result, 1. / p);
861 }
862
863 // other functions
864
865 /**
866 * \brief Functor
867 *
868 * \param A Eigen expression
869 * \param f Pointer-to-function from scalars of \a A to \a OutputScalar
870 * \return Component-wise \f$f(A)\f$, as a dynamic matrix over the
871 * \a OutputScalar scalar field
872 */
873 template <typename OutputScalar, typename Derived>
cwise(const Eigen::MatrixBase<Derived> & A,OutputScalar (* f)(typename Derived::Scalar))874 dyn_mat<OutputScalar> cwise(const Eigen::MatrixBase<Derived>& A,
875 OutputScalar (*f)(typename Derived::Scalar)) {
876 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
877
878 // EXCEPTION CHECKS
879
880 // check zero-size
881 if (!internal::check_nonzero_size(rA))
882 throw exception::ZeroSize("qpp::cwise()");
883 // END EXCEPTION CHECKS
884
885 dyn_mat<OutputScalar> result(rA.rows(), rA.cols());
886
887 #ifdef HAS_OPENMP
888 // NOLINTNEXTLINE
889 #pragma omp parallel for collapse(2)
890 #endif // HAS_OPENMP
891 // column major order for speed
892 for (idx j = 0; j < static_cast<idx>(rA.cols()); ++j)
893 for (idx i = 0; i < static_cast<idx>(rA.rows()); ++i)
894 result(i, j) = (*f)(rA(i, j));
895
896 return result;
897 }
898
899 // Kronecker product of multiple matrices, preserve return type
900 // variadic template
901 /**
902 * \brief Kronecker product
903 * \see qpp::kronpow()
904 *
905 * Used to stop the recursion for the variadic template version of qpp::kron()
906 *
907 * \param head Eigen expression
908 * \return Its argument \a head
909 */
910 template <typename T>
kron(const T & head)911 dyn_mat<typename T::Scalar> kron(const T& head) {
912 return head;
913 }
914
915 /**
916 * \brief Kronecker product
917 * \see qpp::kronpow()
918 *
919 * \param head Eigen expression
920 * \param tail Variadic Eigen expression (zero or more parameters)
921 * \return Kronecker product of all input parameters, evaluated from left to
922 * right, as a dynamic matrix over the same scalar field as its arguments
923 */
924 template <typename T, typename... Args>
kron(const T & head,const Args &...tail)925 dyn_mat<typename T::Scalar> kron(const T& head, const Args&... tail) {
926 return internal::kron2(head, kron(tail...));
927 }
928
929 /**
930 * \brief Kronecker product
931 * \see qpp::kronpow()
932 *
933 * \param As std::vector of Eigen expressions
934 * \return Kronecker product of all elements in \a As, evaluated from left to
935 * right, as a dynamic matrix over the same scalar field as its arguments
936 */
937 template <typename Derived>
kron(const std::vector<Derived> & As)938 dyn_mat<typename Derived::Scalar> kron(const std::vector<Derived>& As) {
939 // EXCEPTION CHECKS
940
941 if (As.empty())
942 throw exception::ZeroSize("qpp::kron()");
943
944 for (auto&& elem : As)
945 if (!internal::check_nonzero_size(elem))
946 throw exception::ZeroSize("qpp::kron()");
947 // END EXCEPTION CHECKS
948
949 dyn_mat<typename Derived::Scalar> result = As[0].derived();
950 for (idx i = 1; i < As.size(); ++i) {
951 result = kron(result, As[i]);
952 }
953
954 return result;
955 }
956
957 // Kronecker product of a list of matrices, preserve return type
958 // deduce the template parameters from initializer_list
959 /**
960 * \brief Kronecker product
961 * \see qpp::kronpow()
962 *
963 * \param As std::initializer_list of Eigen expressions, such as
964 * \a {A1, A2, ... ,Ak}
965 * \return Kronecker product of all elements in \a As, evaluated from left to
966 * right, as a dynamic matrix over the same scalar field as its arguments
967 */
968 template <typename Derived>
969 dyn_mat<typename Derived::Scalar>
kron(const std::initializer_list<Derived> & As)970 kron(const std::initializer_list<Derived>& As) {
971 return kron(std::vector<Derived>(As));
972 }
973
974 /**
975 * \brief Kronecker power
976 * \see qpp::kron()
977 *
978 * \param A Eigen expression
979 * \param n Positive integer
980 * \return Kronecker product of \a A with itself \a n times \f$A^{\otimes n}\f$,
981 * as a dynamic matrix over the same scalar field as \a A
982 */
983 template <typename Derived>
kronpow(const Eigen::MatrixBase<Derived> & A,idx n)984 dyn_mat<typename Derived::Scalar> kronpow(const Eigen::MatrixBase<Derived>& A,
985 idx n) {
986 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
987
988 // EXCEPTION CHECKS
989
990 // check zero-size
991 if (!internal::check_nonzero_size(rA))
992 throw exception::ZeroSize("qpp::kronpow()");
993
994 // check out of range
995 if (n == 0)
996 throw exception::OutOfRange("qpp::kronpow()");
997 // END EXCEPTION CHECKS
998
999 std::vector<dyn_mat<typename Derived::Scalar>> As(n, rA);
1000
1001 return kron(As);
1002 }
1003
1004 // Direct sum of multiple matrices, preserve return type
1005 // variadic template
1006 /**
1007 * \brief Direct sum
1008 * \see qpp::dirsumpow()
1009 *
1010 * Used to stop the recursion for the variadic template version of qpp::dirsum()
1011 *
1012 * \param head Eigen expression
1013 * \return Its argument \a head
1014 */
1015 template <typename T>
dirsum(const T & head)1016 dyn_mat<typename T::Scalar> dirsum(const T& head) {
1017 return head;
1018 }
1019
1020 /**
1021 * \brief Direct sum
1022 * \see qpp::dirsumpow()
1023 *
1024 * \param head Eigen expression
1025 * \param tail Variadic Eigen expression (zero or more parameters)
1026 * \return Direct sum of all input parameters, evaluated from left to right, as
1027 * a dynamic matrix over the same scalar field as its arguments
1028 */
1029 template <typename T, typename... Args>
dirsum(const T & head,const Args &...tail)1030 dyn_mat<typename T::Scalar> dirsum(const T& head, const Args&... tail) {
1031 return internal::dirsum2(head, dirsum(tail...));
1032 }
1033
1034 /**
1035 * \brief Direct sum
1036 * \see qpp::dirsumpow()
1037 *
1038 * \param As std::vector of Eigen expressions
1039 * \return Direct sum of all elements in \a As, evaluated from left to right, as
1040 * a dynamic matrix over the same scalar field as its arguments
1041 */
1042 template <typename Derived>
dirsum(const std::vector<Derived> & As)1043 dyn_mat<typename Derived::Scalar> dirsum(const std::vector<Derived>& As) {
1044 // EXCEPTION CHECKS
1045
1046 if (As.empty())
1047 throw exception::ZeroSize("qpp::dirsum()");
1048
1049 for (auto&& elem : As)
1050 if (!internal::check_nonzero_size(elem))
1051 throw exception::ZeroSize("qpp::dirsum()");
1052 // END EXCEPTION CHECKS
1053
1054 idx total_rows = 0, total_cols = 0;
1055 for (idx i = 0; i < As.size(); ++i) {
1056 total_rows += static_cast<idx>(As[i].rows());
1057 total_cols += static_cast<idx>(As[i].cols());
1058 }
1059 dyn_mat<typename Derived::Scalar> result =
1060 dyn_mat<typename Derived::Scalar>::Zero(total_rows, total_cols);
1061
1062 idx cur_row = 0, cur_col = 0;
1063 for (idx i = 0; i < As.size(); ++i) {
1064 result.block(cur_row, cur_col, As[i].rows(), As[i].cols()) = As[i];
1065 cur_row += static_cast<idx>(As[i].rows());
1066 cur_col += static_cast<idx>(As[i].cols());
1067 }
1068
1069 return result;
1070 }
1071
1072 // Direct sum of a list of matrices, preserve return type
1073 // deduce the template parameters from initializer_list
1074 /**
1075 * \brief Direct sum
1076 * \see qpp::dirsumpow()
1077 *
1078 * \param As std::initializer_list of Eigen expressions,
1079 * such as \a {A1, A2, ... ,Ak}
1080 * \return Direct sum of all elements in \a As, evaluated from left to right, as
1081 * a dynamic matrix over the same scalar field as its arguments
1082 */
1083 template <typename Derived>
1084 dyn_mat<typename Derived::Scalar>
dirsum(const std::initializer_list<Derived> & As)1085 dirsum(const std::initializer_list<Derived>& As) {
1086 return dirsum(std::vector<Derived>(As));
1087 }
1088
1089 /**
1090 * \brief Direct sum power
1091 * \see qpp::dirsum()
1092 *
1093 * \param A Eigen expression
1094 * \param n Positive integer
1095 * \return Direct sum of \a A with itself \a n times \f$A^{\oplus n}\f$, as a
1096 * dynamic matrix over the same scalar field as \a A
1097 */
1098 template <typename Derived>
dirsumpow(const Eigen::MatrixBase<Derived> & A,idx n)1099 dyn_mat<typename Derived::Scalar> dirsumpow(const Eigen::MatrixBase<Derived>& A,
1100 idx n) {
1101 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
1102
1103 // EXCEPTION CHECKS
1104
1105 // check zero-size
1106 if (!internal::check_nonzero_size(rA))
1107 throw exception::ZeroSize("qpp::dirsumpow()");
1108
1109 // check out of range
1110 if (n == 0)
1111 throw exception::OutOfRange("qpp::dirsumpow()");
1112 // END EXCEPTION CHECKS
1113
1114 std::vector<dyn_mat<typename Derived::Scalar>> As(n, rA);
1115
1116 return dirsum(As);
1117 }
1118
1119 /**
1120 * \brief Reshape
1121 *
1122 * Uses column-major order when reshaping (same as MATLAB)
1123 *
1124 * \param A Eigen expression
1125 * \param rows Number of rows of the reshaped matrix
1126 * \param cols Number of columns of the reshaped matrix
1127 * \return Reshaped matrix with \a rows rows and \a cols columns, as a dynamic
1128 * matrix over the same scalar field as \a A
1129 */
1130 template <typename Derived>
reshape(const Eigen::MatrixBase<Derived> & A,idx rows,idx cols)1131 dyn_mat<typename Derived::Scalar> reshape(const Eigen::MatrixBase<Derived>& A,
1132 idx rows, idx cols) {
1133 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
1134
1135 idx Arows = static_cast<idx>(rA.rows());
1136 idx Acols = static_cast<idx>(rA.cols());
1137
1138 // EXCEPTION CHECKS
1139
1140 // check zero-size
1141 if (!internal::check_nonzero_size(rA))
1142 throw exception::ZeroSize("qpp::reshape()");
1143
1144 if (Arows * Acols != rows * cols)
1145 throw exception::DimsMismatchMatrix("qpp::reshape()");
1146 // END EXCEPTION CHECKS
1147
1148 return Eigen::Map<dyn_mat<typename Derived::Scalar>>(
1149 const_cast<typename Derived::Scalar*>(rA.data()), rows, cols);
1150 }
1151
1152 /**
1153 * \brief Commutator
1154 * \see qpp::anticomm()
1155 *
1156 * Commutator \f$[A,B] = AB - BA\f$. Both \a A and \a B must be Eigen
1157 * expressions over the same scalar field.
1158 *
1159 * \param A Eigen expression
1160 * \param B Eigen expression
1161 * \return Commutator \f$AB -BA\f$, as a dynamic matrix over the same scalar
1162 * field as \a A
1163 */
1164 template <typename Derived1, typename Derived2>
comm(const Eigen::MatrixBase<Derived1> & A,const Eigen::MatrixBase<Derived2> & B)1165 dyn_mat<typename Derived1::Scalar> comm(const Eigen::MatrixBase<Derived1>& A,
1166 const Eigen::MatrixBase<Derived2>& B) {
1167 const dyn_mat<typename Derived1::Scalar>& rA = A.derived();
1168 const dyn_mat<typename Derived2::Scalar>& rB = B.derived();
1169
1170 // EXCEPTION CHECKS
1171
1172 // check types
1173 if (!std::is_same<typename Derived1::Scalar,
1174 typename Derived2::Scalar>::value)
1175 throw exception::TypeMismatch("qpp::comm()");
1176
1177 // check zero-size
1178 if (!internal::check_nonzero_size(rA) || !internal::check_nonzero_size(rB))
1179 throw exception::ZeroSize("qpp::comm()");
1180
1181 // check square matrices
1182 if (!internal::check_square_mat(rA) || !internal::check_square_mat(rB))
1183 throw exception::MatrixNotSquare("qpp::comm()");
1184
1185 // check equal dimensions
1186 if (rA.rows() != rB.rows())
1187 throw exception::DimsNotEqual("qpp::comm()");
1188 // END EXCEPTION CHECKS
1189
1190 return rA * rB - rB * rA;
1191 }
1192
1193 /**
1194 * \brief Anti-commutator
1195 * \see qpp::comm()
1196 *
1197 * Anti-commutator \f$\{A,B\} = AB + BA\f$.
1198 * Both \a A and \a B must be Eigen expressions over the same scalar field.
1199 *
1200 * \param A Eigen expression
1201 * \param B Eigen expression
1202 * \return Anti-commutator \f$AB +BA\f$, as a dynamic matrix over the same
1203 * scalar field as \a A
1204 */
1205 template <typename Derived1, typename Derived2>
1206 dyn_mat<typename Derived1::Scalar>
anticomm(const Eigen::MatrixBase<Derived1> & A,const Eigen::MatrixBase<Derived2> & B)1207 anticomm(const Eigen::MatrixBase<Derived1>& A,
1208 const Eigen::MatrixBase<Derived2>& B) {
1209 const dyn_mat<typename Derived1::Scalar>& rA = A.derived();
1210 const dyn_mat<typename Derived2::Scalar>& rB = B.derived();
1211
1212 // EXCEPTION CHECKS
1213
1214 // check types
1215 if (!std::is_same<typename Derived1::Scalar,
1216 typename Derived2::Scalar>::value)
1217 throw exception::TypeMismatch("qpp::anticomm()");
1218
1219 // check zero-size
1220 if (!internal::check_nonzero_size(rA) || !internal::check_nonzero_size(rB))
1221 throw exception::ZeroSize("qpp::anticomm()");
1222
1223 // check square matrices
1224 if (!internal::check_square_mat(rA) || !internal::check_square_mat(rB))
1225 throw exception::MatrixNotSquare("qpp::anticomm()");
1226
1227 // check equal dimensions
1228 if (rA.rows() != rB.rows())
1229 throw exception::DimsNotEqual("qpp::anticomm()");
1230 // END EXCEPTION CHECKS
1231
1232 return rA * rB + rB * rA;
1233 }
1234
1235 /**
1236 * \brief Projector
1237 *
1238 * Normalized projector onto state vector
1239 *
1240 * \param A Eigen expression
1241 * \return Projector onto the state vector \a A, or the matrix \a Zero if \a A
1242 * has norm zero, as a dynamic matrix over the same scalar field as \a A
1243 */
1244 template <typename Derived>
prj(const Eigen::MatrixBase<Derived> & A)1245 dyn_mat<typename Derived::Scalar> prj(const Eigen::MatrixBase<Derived>& A) {
1246 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
1247
1248 // EXCEPTION CHECKS
1249
1250 // check zero-size
1251 if (!internal::check_nonzero_size(rA))
1252 throw exception::ZeroSize("qpp::prj()");
1253
1254 // check column vector
1255 if (!internal::check_cvector(rA))
1256 throw exception::MatrixNotCvector("qpp::prj()");
1257 // END EXCEPTION CHECKS
1258
1259 double normA = norm(rA);
1260 if (normA > 0)
1261 return rA * adjoint(rA) / (normA * normA);
1262 else
1263 return dyn_mat<typename Derived::Scalar>::Zero(rA.rows(), rA.rows());
1264 }
1265
1266 /**
1267 * \brief Gram-Schmidt orthogonalization
1268 *
1269 * \param As std::vector of Eigen expressions as column vectors
1270 * \return Gram-Schmidt vectors of \a As as columns of a dynamic matrix over the
1271 * same scalar field as its arguments
1272 */
1273 template <typename Derived>
grams(const std::vector<Derived> & As)1274 dyn_mat<typename Derived::Scalar> grams(const std::vector<Derived>& As) {
1275 // EXCEPTION CHECKS
1276
1277 // check empty list
1278 if (!internal::check_nonzero_size(As))
1279 throw exception::ZeroSize("qpp::grams()");
1280
1281 for (auto&& elem : As)
1282 if (!internal::check_nonzero_size(elem))
1283 throw exception::ZeroSize("qpp::grams()");
1284
1285 // check that As[0] is a column vector
1286 if (!internal::check_cvector(As[0]))
1287 throw exception::MatrixNotCvector("qpp::grams()");
1288
1289 // now check that all the rest match the size of the first vector
1290 for (auto&& elem : As)
1291 if (elem.rows() != As[0].rows() || elem.cols() != 1)
1292 throw exception::DimsNotEqual("qpp::grams()");
1293 // END EXCEPTION CHECKS
1294
1295 dyn_mat<typename Derived::Scalar> cut =
1296 dyn_mat<typename Derived::Scalar>::Identity(As[0].rows(), As[0].rows());
1297
1298 dyn_mat<typename Derived::Scalar> vi =
1299 dyn_mat<typename Derived::Scalar>::Zero(As[0].rows(), 1);
1300
1301 std::vector<dyn_mat<typename Derived::Scalar>> outvecs;
1302 // find the first non-zero vector in the list
1303 idx pos = 0;
1304 for (pos = 0; pos < As.size(); ++pos) {
1305 if (norm(As[pos]) > 0) // add it as the first element
1306 {
1307 outvecs.emplace_back(As[pos]);
1308 break;
1309 }
1310 }
1311
1312 // start the process
1313 for (idx i = pos + 1; i < As.size(); ++i) {
1314 cut -= prj(outvecs[i - 1 - pos]);
1315 vi = cut * As[i];
1316 outvecs.emplace_back(vi);
1317 }
1318
1319 dyn_mat<typename Derived::Scalar> result(As[0].rows(), outvecs.size());
1320
1321 idx tmp = 0;
1322 for (auto&& elem : outvecs) {
1323 double normA = norm(elem);
1324 if (normA > 0) // we add only the non-zero vectors
1325 {
1326 result.col(tmp++) = elem / normA;
1327 }
1328 }
1329
1330 return result.block(0, 0, As[0].rows(), tmp);
1331 }
1332
1333 // deduce the template parameters from initializer_list
1334 /**
1335 * \brief Gram-Schmidt orthogonalization
1336 *
1337 * \param As std::initializer_list of Eigen expressions as column vectors
1338 * \return Gram-Schmidt vectors of \a As as columns of a dynamic matrix over the
1339 * same scalar field as its arguments
1340 */
1341 template <typename Derived>
1342 dyn_mat<typename Derived::Scalar>
grams(const std::initializer_list<Derived> & As)1343 grams(const std::initializer_list<Derived>& As) {
1344 return grams(std::vector<Derived>(As));
1345 }
1346
1347 /**
1348 * \brief Gram-Schmidt orthogonalization
1349 *
1350 * \param A Eigen expression, the input vectors are the columns of \a A
1351 * \return Gram-Schmidt vectors of the columns of \a A, as columns of a dynamic
1352 * matrix over the same scalar field as \a A
1353 */
1354 template <typename Derived>
grams(const Eigen::MatrixBase<Derived> & A)1355 dyn_mat<typename Derived::Scalar> grams(const Eigen::MatrixBase<Derived>& A) {
1356 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
1357
1358 // EXCEPTION CHECKS
1359
1360 if (!internal::check_nonzero_size(rA))
1361 throw exception::ZeroSize("qpp::grams()");
1362 // END EXCEPTION CHECKS
1363
1364 std::vector<dyn_mat<typename Derived::Scalar>> input;
1365
1366 for (idx i = 0; i < static_cast<idx>(rA.cols()); ++i)
1367 input.emplace_back(rA.col(i));
1368
1369 return grams<dyn_mat<typename Derived::Scalar>>(input);
1370 }
1371
1372 /**
1373 * \brief Non-negative integer index to multi-index
1374 * \see qpp::multiidx2n()
1375 *
1376 * Uses standard lexicographical order, i.e., 00...0, 00...1 etc.
1377 *
1378 * \param n Non-negative integer index
1379 * \param dims Dimensions of the multi-partite system
1380 * \return Multi-index of the same size as \a dims
1381 */
n2multiidx(idx n,const std::vector<idx> & dims)1382 inline std::vector<idx> n2multiidx(idx n, const std::vector<idx>& dims) {
1383 // EXCEPTION CHECKS
1384
1385 if (dims.size() > internal::maxn)
1386 throw exception::OutOfRange("qpp::n2multiidx()");
1387
1388 if (!internal::check_dims(dims)) {
1389 throw exception::DimsInvalid("qpp::n2multiidx()");
1390 }
1391
1392 if (n >= std::accumulate(std::begin(dims), std::end(dims),
1393 static_cast<idx>(1), std::multiplies<idx>())) {
1394 throw exception::OutOfRange("qpp::n2multiidx()");
1395 }
1396 // END EXCEPTION CHECKS
1397
1398 // double the size for matrices reshaped as vectors
1399 idx result[2 * internal::maxn];
1400 internal::n2multiidx(n, dims.size(), dims.data(), result);
1401
1402 return std::vector<idx>(result, result + dims.size());
1403 }
1404
1405 /**
1406 * \brief Multi-index to non-negative integer index
1407 * \see qpp::n2multiidx()
1408 *
1409 * Uses standard lexicographical order, i.e., 00...0, 00...1 etc.
1410 *
1411 * \param midx Multi-index
1412 * \param dims Dimensions of the multi-partite system
1413 * \return Non-negative integer index
1414 */
multiidx2n(const std::vector<idx> & midx,const std::vector<idx> & dims)1415 inline idx multiidx2n(const std::vector<idx>& midx,
1416 const std::vector<idx>& dims) {
1417 // EXCEPTION CHECKS
1418 if (midx.size() != dims.size())
1419 throw exception::SizeMismatch("qpp::multiidx2n()");
1420
1421 if (!internal::check_dims(dims))
1422 throw exception::DimsInvalid("qpp::multiidx2n()");
1423
1424 if (dims.size() > internal::maxn)
1425 throw exception::OutOfRange("qpp::multiidx2n()");
1426
1427 for (idx i = 0; i < dims.size(); ++i)
1428 if (midx[i] >= dims[i]) {
1429 throw exception::OutOfRange("qpp::multiidx2n()");
1430 }
1431 // END EXCEPTION CHECKS
1432
1433 return internal::multiidx2n(midx.data(), dims.size(), dims.data());
1434 }
1435
1436 /**
1437 * \brief Multi-partite qudit ket
1438 * \see qpp::operator "" _ket()
1439 *
1440 * Constructs the multi-partite qudit ket \f$|\mathrm{mask}\rangle\f$,
1441 * where \a mask is a std::vector of non-negative integers. Each element in
1442 * \a mask has to be smaller than the corresponding element in \a dims.
1443 *
1444 * \param mask std::vector of non-negative integers
1445 * \param dims Dimensions of the multi-partite system
1446 * \return Multi-partite qudit state vector, as a complex dynamic column vector
1447 */
mket(const std::vector<idx> & mask,const std::vector<idx> & dims)1448 inline ket mket(const std::vector<idx>& mask, const std::vector<idx>& dims) {
1449 idx n = mask.size();
1450
1451 idx D = std::accumulate(std::begin(dims), std::end(dims),
1452 static_cast<idx>(1), std::multiplies<idx>());
1453
1454 // EXCEPTION CHECKS
1455
1456 // check zero size
1457 if (n == 0)
1458 throw exception::ZeroSize("qpp::mket()");
1459 // check valid dims
1460 if (!internal::check_dims(dims))
1461 throw exception::DimsInvalid("qpp::mket()");
1462 // check mask and dims have the same size
1463 if (mask.size() != dims.size())
1464 throw exception::SubsysMismatchDims("qpp::mket()");
1465 // check mask is a valid vector
1466 for (idx i = 0; i < n; ++i)
1467 if (mask[i] >= dims[i])
1468 throw exception::SubsysMismatchDims("qpp::mket()");
1469 // END EXCEPTION CHECKS
1470
1471 ket result = ket::Zero(D);
1472 idx pos = multiidx2n(mask, dims);
1473 result(pos) = 1;
1474
1475 return result;
1476 }
1477
1478 /**
1479 * \brief Multi-partite qudit ket
1480 * \see qpp::operator "" _ket()
1481 *
1482 * Constructs the multi-partite qudit ket \f$|\mathrm{mask}\rangle\f$, all
1483 * subsystem having equal dimension \a d. \a mask is a std::vector of
1484 * non-negative integers, and each element in \a mask has to be strictly smaller
1485 * than \a d.
1486 *
1487 * \param mask std::vector of non-negative integers
1488 * \param d Subsystem dimensions
1489 * \return Multi-partite qudit state vector, as a complex dynamic column vector
1490 */
mket(const std::vector<idx> & mask,idx d=2)1491 inline ket mket(const std::vector<idx>& mask, idx d = 2) {
1492 idx n = mask.size();
1493 idx D = static_cast<idx>(std::llround(std::pow(d, n)));
1494
1495 // EXCEPTION CHECKS
1496
1497 // check zero size
1498 if (n == 0)
1499 throw exception::ZeroSize("qpp::mket()");
1500
1501 // check valid dims
1502 if (d == 0)
1503 throw exception::DimsInvalid("qpp::mket()");
1504
1505 // check mask is a valid vector
1506 for (idx i = 0; i < n; ++i)
1507 if (mask[i] >= d)
1508 throw exception::SubsysMismatchDims("qpp::mket()");
1509 // END EXCEPTION CHECKS
1510
1511 ket result = ket::Zero(D);
1512 std::vector<idx> dims(n, d);
1513 idx pos = multiidx2n(mask, dims);
1514 result(pos) = 1;
1515
1516 return result;
1517 }
1518
1519 /**
1520 * \brief Projector onto multi-partite qudit ket
1521 * \see qpp::operator "" _prj()
1522 *
1523 * Constructs the projector onto the multi-partite qudit ket
1524 * \f$|\mathrm{mask}\rangle\f$, where \a mask is a std::vector of non-negative
1525 * integers. Each element in \a mask has to be smaller than the corresponding
1526 * element in \a dims.
1527 *
1528 * \param mask std::vector of non-negative integers
1529 * \param dims Dimensions of the multi-partite system
1530 * \return Projector onto multi-partite qudit state vector, as a complex dynamic
1531 * matrix
1532 */
mprj(const std::vector<idx> & mask,const std::vector<idx> & dims)1533 inline cmat mprj(const std::vector<idx>& mask, const std::vector<idx>& dims) {
1534 idx n = mask.size();
1535
1536 idx D = std::accumulate(std::begin(dims), std::end(dims),
1537 static_cast<idx>(1), std::multiplies<idx>());
1538
1539 // EXCEPTION CHECKS
1540
1541 // check zero size
1542 if (n == 0)
1543 throw exception::ZeroSize("qpp::mprj()");
1544 // check valid dims
1545 if (!internal::check_dims(dims))
1546 throw exception::DimsInvalid("qpp::mprj()");
1547 // check mask and dims have the same size
1548 if (mask.size() != dims.size())
1549 throw exception::SubsysMismatchDims("qpp::mprj()");
1550 // check mask is a valid vector
1551 for (idx i = 0; i < n; ++i)
1552 if (mask[i] >= dims[i])
1553 throw exception::SubsysMismatchDims("qpp::mprj()");
1554 // END EXCEPTION CHECKS
1555
1556 cmat result = cmat::Zero(D, D);
1557 idx pos = multiidx2n(mask, dims);
1558 result(pos, pos) = 1;
1559
1560 return result;
1561 }
1562
1563 /**
1564 * \brief Projector onto multi-partite qudit ket
1565 * \see qpp::operator "" _prj()
1566 *
1567 * Constructs the projector onto the multi-partite qudit ket
1568 * \f$|\mathrm{mask}\rangle\f$, all subsystem having equal dimension \a d.
1569 * \a mask is a std::vector of non-negative integers, and each element in
1570 * \a mask has to be strictly smaller than \a d.
1571 *
1572 * \param mask std::vector of non-negative integers
1573 * \param d Subsystem dimensions
1574 * \return Projector onto multi-partite qudit state vector, as a complex dynamic
1575 * matrix
1576 */
mprj(const std::vector<idx> & mask,idx d=2)1577 inline cmat mprj(const std::vector<idx>& mask, idx d = 2) {
1578 idx n = mask.size();
1579 idx D = static_cast<idx>(std::llround(std::pow(d, n)));
1580
1581 // EXCEPTION CHECKS
1582
1583 // check zero size
1584 if (n == 0)
1585 throw exception::ZeroSize("qpp::mprj()");
1586
1587 // check valid dims
1588 if (d == 0)
1589 throw exception::DimsInvalid("qpp::mprj()");
1590
1591 // check mask is a valid vector
1592 for (idx i = 0; i < n; ++i)
1593 if (mask[i] >= d)
1594 throw exception::SubsysMismatchDims("qpp::mprj()");
1595 // END EXCEPTION CHECKS
1596
1597 cmat result = cmat::Zero(D, D);
1598 std::vector<idx> dims(n, d);
1599 idx pos = multiidx2n(mask, dims);
1600 result(pos, pos) = 1;
1601
1602 return result;
1603 }
1604
1605 /**
1606 * \brief Computes the absolute values squared of an STL-like range of complex
1607 * numbers
1608
1609 * \param first Iterator to the first element of the range
1610 * \param last Iterator to the last element of the range
1611 * \return Real vector consisting of the range absolute values squared
1612 */
1613 template <typename InputIterator>
abssq(InputIterator first,InputIterator last)1614 std::vector<double> abssq(InputIterator first, InputIterator last) {
1615 std::vector<double> weights(std::distance(first, last));
1616 std::transform(first, last, std::begin(weights),
1617 [](cplx z) -> double { return std::norm(z); });
1618
1619 return weights;
1620 }
1621
1622 /**
1623 * \brief Computes the absolute values squared of an STL-like container
1624 *
1625 * \param c STL-like container
1626 * \return Real vector consisting of the container's absolute values squared
1627 */
1628 template <typename Container>
1629 std::vector<double>
abssq(const Container & c,typename std::enable_if<is_iterable<Container>::value>::type * =nullptr)1630 abssq(const Container& c,
1631 typename std::enable_if<is_iterable<Container>::value>::type* = nullptr)
1632 // we need the std::enable_if to SFINAE out Eigen expressions
1633 // that will otherwise match, instead of matching
1634 // the overload below:
1635
1636 // template<typename Derived>
1637 // abssq(const Eigen::MatrixBase<Derived>& A)
1638 {
1639 return abssq(std::begin(c), std::end(c));
1640 }
1641
1642 /**
1643 * \brief Computes the absolute values squared of an Eigen expression
1644
1645 * \param A Eigen expression
1646 * \return Real vector consisting of the absolute values squared
1647 */
1648 template <typename Derived>
abssq(const Eigen::MatrixBase<Derived> & A)1649 std::vector<double> abssq(const Eigen::MatrixBase<Derived>& A) {
1650 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
1651
1652 // EXCEPTION CHECKS
1653
1654 // check zero-size
1655 if (!internal::check_nonzero_size(rA))
1656 throw exception::ZeroSize("qpp::abssq()");
1657 // END EXCEPTION CHECKS
1658
1659 return abssq(rA.data(), rA.data() + rA.size());
1660 }
1661
1662 /**
1663 * \brief Element-wise sum of an STL-like range
1664 *
1665 * \param first Iterator to the first element of the range
1666 * \param last Iterator to the last element of the range
1667 * \return Element-wise sum of the range, as a scalar over the same scalar
1668 * field as the range
1669 */
1670 template <typename InputIterator>
1671 typename std::iterator_traits<InputIterator>::value_type
sum(InputIterator first,InputIterator last)1672 sum(InputIterator first, InputIterator last) {
1673 using value_type = typename std::iterator_traits<InputIterator>::value_type;
1674
1675 return std::accumulate(first, last, static_cast<value_type>(0));
1676 }
1677
1678 /**
1679 * \brief Element-wise sum of the elements of an STL-like container
1680 *
1681 * \param c STL-like container
1682 * \return Element-wise sum of the elements of the container,
1683 * as a scalar over the same scalar field as the container
1684 */
1685 template <typename Container>
1686 typename Container::value_type
sum(const Container & c,typename std::enable_if<is_iterable<Container>::value>::type * =nullptr)1687 sum(const Container& c,
1688 typename std::enable_if<is_iterable<Container>::value>::type* = nullptr) {
1689 return sum(std::begin(c), std::end(c));
1690 }
1691
1692 /**
1693 * \brief Element-wise product of an STL-like range
1694 *
1695 * \param first Iterator to the first element of the range
1696 * \param last Iterator to the last element of the range
1697 * \return Element-wise product of the range, as a scalar over the same scalar
1698 * field as the range
1699 */
1700 template <typename InputIterator>
1701 typename std::iterator_traits<InputIterator>::value_type
prod(InputIterator first,InputIterator last)1702 prod(InputIterator first, InputIterator last) {
1703 using value_type = typename std::iterator_traits<InputIterator>::value_type;
1704
1705 return std::accumulate(first, last, static_cast<value_type>(1),
1706 std::multiplies<value_type>());
1707 }
1708
1709 /**
1710 * \brief Element-wise product of the elements of an STL-like container
1711 *
1712 * \param c STL-like container
1713 * \return Element-wise product of the elements of the container, as a scalar
1714 * over the same scalar field as the container
1715 */
1716 template <typename Container>
1717 typename Container::value_type
prod(const Container & c,typename std::enable_if<is_iterable<Container>::value>::type * =nullptr)1718 prod(const Container& c,
1719 typename std::enable_if<is_iterable<Container>::value>::type* = nullptr) {
1720 return prod(std::begin(c), std::end(c));
1721 }
1722
1723 /**
1724 * \brief Finds the pure state representation of a matrix proportional to a
1725 * projector onto a pure state
1726 *
1727 * \note No purity check is done, the input state \a A must have rank one,
1728 * otherwise the function returns the first non-zero eigenvector of \a A
1729 *
1730 * \param A Eigen expression, assumed to be proportional to a projector onto a
1731 * pure state, i.e., \a A is assumed to have rank one
1732 * \return The unique non-zero eigenvector of \a A (up to a phase), as a
1733 * dynamic column vector over the same scalar field as \a A
1734 */
1735 template <typename Derived>
1736 dyn_col_vect<typename Derived::Scalar>
rho2pure(const Eigen::MatrixBase<Derived> & A)1737 rho2pure(const Eigen::MatrixBase<Derived>& A) {
1738 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
1739
1740 // EXCEPTION CHECKS
1741 // check zero-size
1742 if (!internal::check_nonzero_size(rA))
1743 throw exception::ZeroSize("qpp::rho2pure()");
1744 // check square matrix
1745 if (!internal::check_square_mat(rA))
1746 throw exception::MatrixNotSquare("qpp::rho2pure()");
1747 // END EXCEPTION CHECKS
1748
1749 dyn_col_vect<double> tmp_evals = hevals(rA);
1750 cmat tmp_evects = hevects(rA);
1751 dyn_col_vect<typename Derived::Scalar> result =
1752 dyn_col_vect<typename Derived::Scalar>::Zero(rA.rows());
1753 // find the non-zero eigenvector
1754 // there is only one, assuming the state is pure
1755 for (idx k = 0; k < static_cast<idx>(rA.rows()); ++k) {
1756 if (std::abs(tmp_evals(k)) > 0) {
1757 result = tmp_evects.col(k);
1758 break;
1759 }
1760 }
1761
1762 return result;
1763 }
1764
1765 /**
1766 * \brief Constructs the complement of a subsystem vector
1767 *
1768 * \param subsys Subsystem vector
1769 * \param n Total number of systems
1770 * \return Complement of \a subsys with respect to the set
1771 * \f$\{0, 1, \ldots, n - 1\}\f$
1772 */
complement(std::vector<idx> subsys,idx n)1773 inline std::vector<idx> complement(std::vector<idx> subsys, idx n) {
1774 // EXCEPTION CHECKS
1775
1776 if (n < subsys.size())
1777 throw exception::OutOfRange("qpp::complement()");
1778 for (idx s : subsys)
1779 if (s >= n)
1780 throw exception::SubsysMismatchDims("qpp::complement()");
1781 // END EXCEPTION CHECKS
1782
1783 std::vector<idx> all(n);
1784 std::vector<idx> subsys_bar(n - subsys.size());
1785
1786 std::iota(std::begin(all), std::end(all), 0);
1787 std::sort(std::begin(subsys), std::end(subsys));
1788 std::set_difference(std::begin(all), std::end(all), std::begin(subsys),
1789 std::end(subsys), std::begin(subsys_bar));
1790
1791 return subsys_bar;
1792 }
1793
1794 /**
1795 * \brief Computes the 3-dimensional real Bloch vector corresponding to the
1796 * qubit density matrix \a A
1797 * \see qpp::bloch2rho()
1798 *
1799 * \note It is implicitly assumed that the density matrix is Hermitian
1800 *
1801 * \param A Eigen expression
1802 * \return 3-dimensional Bloch vector
1803 */
1804 template <typename Derived>
rho2bloch(const Eigen::MatrixBase<Derived> & A)1805 std::vector<double> rho2bloch(const Eigen::MatrixBase<Derived>& A) {
1806 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
1807
1808 // EXCEPTION CHECKS
1809
1810 // check qubit matrix
1811 if (!internal::check_qubit_matrix(rA))
1812 throw exception::NotQubitMatrix("qpp::rho2bloch()");
1813 // END EXCEPTION CHECKS
1814
1815 std::vector<double> result(3);
1816 cmat X(2, 2), Y(2, 2), Z(2, 2);
1817 X << 0, 1, 1, 0;
1818 Y << 0, -1_i, 1_i, 0;
1819 Z << 1, 0, 0, -1;
1820 result[0] = std::real(trace(rA * X));
1821 result[1] = std::real(trace(rA * Y));
1822 result[2] = std::real(trace(rA * Z));
1823
1824 return result;
1825 }
1826
1827 /**
1828 * \brief Computes the density matrix corresponding to the 3-dimensional real
1829 * Bloch vector \a r
1830 * \see qpp::rho2bloch()
1831 *
1832 * \param r 3-dimensional real vector
1833 * \return Qubit density matrix
1834 */
bloch2rho(const std::vector<double> & r)1835 inline cmat bloch2rho(const std::vector<double>& r) {
1836 // EXCEPTION CHECKS
1837
1838 // check 3-dimensional vector
1839 if (r.size() != 3)
1840 throw exception::CustomException("qpp::bloch2rho",
1841 "r is not a 3-dimensional vector!");
1842 // END EXCEPTION CHECKS
1843
1844 cmat X(2, 2), Y(2, 2), Z(2, 2), Id2(2, 2);
1845 X << 0, 1, 1, 0;
1846 Y << 0, -1_i, 1_i, 0;
1847 Z << 1, 0, 0, -1;
1848 Id2 << 1, 0, 0, 1;
1849
1850 return (Id2 + r[0] * X + r[1] * Y + r[2] * Z) / 2.;
1851 }
1852
1853 /**
1854 * \brief Extracts the dits from a normalized multi-partite pure state in the
1855 * computational basis. Behaves like the "inverse" of qpp::mket().
1856 * \see qpp::mket()
1857 *
1858 * \note Assumes \a psi is a normalized state vector (ket) in the computational
1859 * basis, up to a phase; finds the first coefficient of \a psi close to 1 up to
1860 * \a precision, and returns the digit representation of the corresponding state
1861 * with a single 1 in that position. If there's no such coefficient (i.e., the
1862 * state is not a computational basis state up to a phase), returns the empty
1863 * vector.
1864 *
1865 * \param psi Column vector Eigen expression
1866 * \param dims Dimensions of the multi-partite system
1867 * \param precision Numerical precision (how close to 1 should the nonzero
1868 * coefficient be)
1869 * \return Vector containing the digit representation of \a psi, or the empty
1870 * vector if \a psi is not a computational basis state up to a phase.
1871 */
1872 template <typename Derived>
zket2dits(const Eigen::MatrixBase<Derived> & psi,const std::vector<idx> & dims,double precision=1e-12)1873 std::vector<idx> zket2dits(const Eigen::MatrixBase<Derived>& psi,
1874 const std::vector<idx>& dims,
1875 double precision = 1e-12) {
1876 const dyn_col_vect<typename Derived::Scalar>& rpsi = psi.derived();
1877
1878 // EXCEPTION CHECKS
1879
1880 // check zero-size
1881 if (!internal::check_nonzero_size(rpsi))
1882 throw exception::ZeroSize("qpp::zket2dits()");
1883
1884 // check column vector
1885 if (!internal::check_cvector(rpsi))
1886 throw exception::MatrixNotCvector("qpp::zket2dits()");
1887
1888 // check that dims is a valid dimension vector
1889 if (!internal::check_dims(dims))
1890 throw exception::DimsInvalid("qpp::zket2dits()");
1891
1892 // check that dims match psi column vector
1893 if (!internal::check_dims_match_cvect(dims, rpsi))
1894 throw exception::DimsMismatchCvector("qpp::zket2dits()");
1895 // END EXCEPTION CHECKS
1896
1897 auto N = static_cast<idx>(rpsi.size());
1898 for (idx i = 0; i < N; ++i) {
1899 if (std::abs(std::abs(rpsi[i]) - 1.0) < precision) {
1900 return n2multiidx(i, dims);
1901 }
1902 }
1903
1904 return {};
1905 }
1906
1907 /**
1908 * \brief Extracts the dits from a normalized multi-partite pure state in the
1909 * computational basis, all subsystem having equal dimension \a d. Behaves like
1910 * the "inverse" of qpp::mket().
1911 * \see qpp::mket()
1912 *
1913 * \note Assumes \a psi is a normalized state vector (ket) in the computational
1914 * basis, up to a phase; finds the first coefficient of \a psi close to 1 up to
1915 * \a precision, and returns the digit representation of the corresponding state
1916 * with a single 1 in that position. If there's no such coefficient (i.e., the
1917 * state is not a computational basis state up to a phase), returns the empty
1918 * vector.
1919 *
1920 * \param psi Column vector Eigen expression
1921 * \param d Subsystem dimensions
1922 * \param precision Numerical precision (how close to 1 should the nonzero
1923 * coefficient be)
1924 * \return Vector containing the digit representation of \a psi, or the empty
1925 * vector if \a psi is not a computational basis state up to a phase.
1926 */
1927 template <typename Derived>
zket2dits(const Eigen::MatrixBase<Derived> & psi,idx d=2,double precision=1e-12)1928 std::vector<idx> zket2dits(const Eigen::MatrixBase<Derived>& psi, idx d = 2,
1929 double precision = 1e-12) {
1930 const dyn_col_vect<typename Derived::Scalar>& rpsi = psi.derived();
1931
1932 // EXCEPTION CHECKS
1933 // check valid dims
1934 if (d < 2)
1935 throw exception::DimsInvalid("qpp::zket2dits()");
1936
1937 // END EXCEPTION CHECKS
1938
1939 idx n = internal::get_num_subsys(static_cast<idx>(rpsi.rows()), d);
1940 std::vector<idx> dims(n, d); // local dimensions vector
1941
1942 return zket2dits(psi, dims, precision);
1943 }
1944
1945 inline namespace literals {
1946 // Idea taken from http://techblog.altplus.co.jp/entry/2017/11/08/130921
1947 /**
1948 * \brief Multi-partite qubit ket user-defined literal
1949 * \see qpp::mket()
1950 *
1951 * Constructs the multi-partite qubit ket \f$|\mathrm{Bits}\rangle\f$
1952 *
1953 * \tparam Bits String of binary numbers representing the qubit ket
1954 * \return Multi-partite qubit ket, as a complex dynamic column vector
1955 */
1956 template <char... Bits>
operator ""_ket()1957 ket operator"" _ket() {
1958 constexpr idx n = sizeof...(Bits);
1959 constexpr char bits[n + 1] = {Bits..., '\0'};
1960 qpp::ket q = qpp::ket::Zero(static_cast<idx>(std::llround(std::pow(2, n))));
1961 idx pos = 0;
1962
1963 // EXCEPTION CHECKS
1964
1965 // check valid multi-partite qubit state
1966 for (idx i = 0; i < n; ++i) {
1967 if (bits[i] != '0' && bits[i] != '1')
1968 throw exception::OutOfRange(R"xxx(qpp::operator "" _ket())xxx");
1969 }
1970 // END EXCEPTION CHECKS
1971
1972 pos = std::stoi(bits, nullptr, 2);
1973 q(pos) = 1;
1974
1975 return q;
1976 }
1977
1978 /**
1979 * \brief Multi-partite qubit bra user-defined literal
1980 * \see qpp::mket() and qpp::adjoint()
1981 *
1982 * Constructs the multi-partite qubit bra \f$\langle\mathrm{Bits}|\f$
1983 *
1984 * \tparam Bits String of binary numbers representing the qubit bra
1985 * \return Multi-partite qubit bra, as a complex dynamic row vector
1986 */
1987 template <char... Bits>
operator ""_bra()1988 bra operator"" _bra() {
1989 constexpr idx n = sizeof...(Bits);
1990 constexpr char bits[n + 1] = {Bits..., '\0'};
1991 qpp::bra q = qpp::ket::Zero(static_cast<idx>(std::llround(std::pow(2, n))));
1992 idx pos = 0;
1993
1994 // EXCEPTION CHECKS
1995
1996 // check valid multi-partite qubit state
1997 for (idx i = 0; i < n; ++i) {
1998 if (bits[i] != '0' && bits[i] != '1')
1999 throw exception::OutOfRange(R"xxx(qpp::operator "" _bra())xxx");
2000 }
2001 // END EXCEPTION CHECKS
2002
2003 pos = std::stoi(bits, nullptr, 2);
2004 q(pos) = 1;
2005
2006 return q;
2007 }
2008
2009 /**
2010 * \brief Multi-partite qubit projector user-defined literal
2011 * \see qpp::mprj()
2012 *
2013 * Constructs the multi-partite qubit projector
2014 * \f$|\mathrm{Bits}\rangle\langle\mathrm{Bits}|\f$ (in the computational basis)
2015 *
2016 * \tparam Bits String of binary numbers representing the qubit state to project
2017 * on
2018 * \return Multi-partite qubit projector, as a complex dynamic matrix
2019 */
2020 template <char... Bits>
operator ""_prj()2021 cmat operator"" _prj() {
2022 constexpr idx n = sizeof...(Bits);
2023 constexpr char bits[n + 1] = {Bits..., '\0'};
2024
2025 // EXCEPTION CHECKS
2026
2027 // check valid multi-partite qubit state
2028 for (idx i = 0; i < n; ++i) {
2029 if (bits[i] != '0' && bits[i] != '1')
2030 throw exception::OutOfRange(R"xxx(qpp::operator "" _prj())xxx");
2031 }
2032 // END EXCEPTION CHECKS
2033
2034 return kron(operator""_ket<Bits...>(), operator""_bra<Bits...>());
2035 }
2036 } /* namespace literals */
2037
2038 namespace internal {
2039 // hash combine, code taken from boost::hash_combine(), see
2040 // https://www.boost.org/doc/libs/1_69_0/doc/html/hash/reference.html#boost.hash_combine
2041 /**
2042 * \brief Hash combine
2043 *
2044 * \tparam T Type
2045 * \param seed Initial seed, will be updated by the function
2046 * \param v Value with which the hash is combined
2047 */
2048 template <class T>
hash_combine(std::size_t & seed,const T & v)2049 void hash_combine(std::size_t& seed, const T& v) {
2050 std::hash<T> hasher;
2051 seed ^= hasher(v) + 0x9e3779b9 + (seed << static_cast<std::size_t>(6)) +
2052 (seed >> static_cast<std::size_t>(2));
2053 }
2054 } /* namespace internal */
2055
2056 /**
2057 * \brief Computes the hash of en Eigen matrix/vector/expression
2058 * \note Code taken from boost::hash_combine(), see
2059 * https://www.boost.org/doc/libs/1_69_0/doc/html/hash/reference.html#boost.hash_combine
2060 *
2061 * \param A Eigen expression
2062 * \param seed Seed, 0 by default
2063 * \return Hash of its argument
2064 */
2065 template <typename Derived>
hash_eigen(const Eigen::MatrixBase<Derived> & A,std::size_t seed=0)2066 std::size_t hash_eigen(const Eigen::MatrixBase<Derived>& A,
2067 std::size_t seed = 0) {
2068 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
2069
2070 // EXCEPTION CHECKS
2071
2072 // check zero-size
2073 if (!internal::check_nonzero_size(rA))
2074 throw exception::ZeroSize("qpp::hash_eigen()");
2075 // END EXCEPTION CHECKS
2076
2077 auto* p = rA.data();
2078 idx sizeA = static_cast<idx>(rA.size());
2079 for (idx i = 0; i < sizeA; ++i) {
2080 internal::hash_combine(seed, std::real(p[i]));
2081 internal::hash_combine(seed, std::imag(p[i]));
2082 }
2083
2084 return seed;
2085 }
2086
2087 namespace internal {
2088 /**
2089 * \class qpp::internal::HashEigen
2090 * \brief Functor for hashing Eigen expressions
2091 */
2092 struct HashEigen {
2093 template <typename Derived>
operator ()qpp::internal::HashEigen2094 std::size_t operator()(const Eigen::MatrixBase<Derived>& A) const {
2095 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
2096 return hash_eigen(rA);
2097 }
2098 };
2099
2100 /**
2101 * \class qpp::internal::EqualEigen
2102 * \brief Functor for comparing Eigen expressions for equality
2103 * \note Works without assertion fails even if the dimensions of the arguments
2104 * are different (in which case it simply returns false)
2105 */
2106 struct EqualEigen {
2107 template <typename Derived>
operator ()qpp::internal::EqualEigen2108 bool operator()(const Eigen::MatrixBase<Derived>& A,
2109 const Eigen::MatrixBase<Derived>& B) const {
2110 const dyn_mat<typename Derived::Scalar>& rA = A.derived();
2111 const dyn_mat<typename Derived::Scalar>& rB = B.derived();
2112 if (rA.rows() == rB.rows() && rA.cols() == rB.cols())
2113 return rA == rB;
2114 else
2115 return false;
2116 }
2117 };
2118
2119 /**
2120 * \class qpp::internal::EqualSameSizeStringDits
2121 * \brief Functor for comparing strings of numbers of equal sizes in
2122 * lexicographical order. Establishes a strict weak ordering relation.
2123 * \note Used as a hash table comparator in qpp::QEngine
2124 */
2125 struct EqualSameSizeStringDits {
operator ()qpp::internal::EqualSameSizeStringDits2126 bool operator()(const std::string& s1, const std::string& s2) const {
2127 std::vector<std::string> tk1, tk2;
2128 std::string w1, w2;
2129 std::stringstream ss1{s1}, ss2{s2};
2130
2131 // tokenize the string into words (assumes words are separated by space)
2132 while (ss1 >> w1)
2133 tk1.emplace_back(w1);
2134 while (ss2 >> w2)
2135 tk2.emplace_back(w2);
2136
2137 // compare lexicographically
2138 auto it1 = std::begin(tk1);
2139 auto it2 = std::begin(tk2);
2140 while (it1 != std::end(tk1) && it2 != std::end(tk2)) {
2141 auto n1 = std::stoll(*it1++);
2142 auto n2 = std::stoll(*it2++);
2143 if (n1 < n2)
2144 return true;
2145 else if (n1 == n2)
2146 continue;
2147 else if (n1 > n2)
2148 return false;
2149 }
2150 return false;
2151 }
2152 };
2153
2154 } /* namespace internal */
2155 } /* namespace qpp */
2156
2157 #endif /* FUNCTIONS_HPP_ */
2158