Skip to content

Commit 6e15803

Browse files
✨ Enhance Matrix functionality with new methods and optimizations (#1802)
1 parent 43709be commit 6e15803

4 files changed

Lines changed: 1112 additions & 51 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ with the exception that minor releases may include breaking changes.
4242
[#1569], [#1570], [#1572], [#1573], [#1580], [#1602], [#1620], [#1623],
4343
[#1624], [#1626], [#1627], [#1635], [#1638], [#1673], [#1675], [#1700],
4444
[#1710], [#1717], [#1728], [#1730], [#1749], [#1751], [#1762], [#1765],
45-
[#1774], [#1780], [#1781], [#1782], [#1787])
45+
[#1774], [#1780], [#1781], [#1782], [#1787], [#1802])
4646
([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**],
4747
[**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**],
4848
[**@simon1hofmann**])
@@ -598,6 +598,7 @@ changelogs._
598598

599599
<!-- PR links -->
600600

601+
[#1802]: https://github.com/munich-quantum-toolkit/core/pull/1802
601602
[#1787]: https://github.com/munich-quantum-toolkit/core/pull/1787
602603
[#1782]: https://github.com/munich-quantum-toolkit/core/pull/1782
603604
[#1781]: https://github.com/munich-quantum-toolkit/core/pull/1781

mlir/include/mlir/Dialect/QCO/Utils/Matrix.h

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
#pragma once
1212

13+
#include <llvm/ADT/ArrayRef.h>
14+
#include <mlir/Support/LLVM.h>
15+
1316
#include <array>
1417
#include <complex>
1518
#include <cstddef>
@@ -26,6 +29,8 @@ using Complex = std::complex<double>;
2629
inline constexpr double MATRIX_TOLERANCE = 1e-14;
2730

2831
class DynamicMatrix;
32+
struct Matrix4x4;
33+
struct SymmetricEigen4;
2934

3035
/**
3136
* @brief 1x1 matrix for global-phase gates.
@@ -183,6 +188,12 @@ struct Matrix2x2 {
183188
*/
184189
[[nodiscard]] Matrix2x2 adjoint() const;
185190

191+
/**
192+
* @brief Returns the (non-conjugate) transpose of this matrix.
193+
* @return Transposed matrix `A^T`.
194+
*/
195+
[[nodiscard]] Matrix2x2 transpose() const;
196+
186197
/**
187198
* @brief Returns the trace of this matrix.
188199
* @return Sum of diagonal entries.
@@ -195,6 +206,13 @@ struct Matrix2x2 {
195206
*/
196207
[[nodiscard]] Complex determinant() const;
197208

209+
/**
210+
* @brief Checks whether this matrix is approximately the identity.
211+
* @param tol Maximum allowed complex modulus of each entry difference.
212+
* @return True if every entry is within @p tol of the identity.
213+
*/
214+
[[nodiscard]] bool isIdentity(double tol = MATRIX_TOLERANCE) const;
215+
198216
/**
199217
* @brief Checks approximate equality using an absolute entry-wise tolerance.
200218
*
@@ -215,6 +233,30 @@ struct Matrix2x2 {
215233
* @return `true` when @p src is 2x2.
216234
*/
217235
[[nodiscard]] bool assignFrom(const DynamicMatrix& src);
236+
237+
/**
238+
* @brief Embed this single-qubit matrix into an @p numQubits-qubit Hilbert
239+
* space.
240+
*
241+
* Wire @p qubitIndex uses the same MSB-first convention as @ref
242+
* Matrix4x4::kron (high bit first operand, low bit second). For each basis
243+
* pair whose untouched wires match, copies this matrix at the target qubit's
244+
* row/column bits.
245+
*
246+
* @param numQubits Number of qubits in the target Hilbert space.
247+
* @param qubitIndex Wire index to act on.
248+
* @return Embedded unitary as a dynamic matrix.
249+
*/
250+
[[nodiscard]] DynamicMatrix embedInNqubit(std::size_t numQubits,
251+
std::size_t qubitIndex) const;
252+
253+
/**
254+
* @brief Embed this single-qubit matrix into a two-qubit Hilbert space.
255+
*
256+
* @param qubitIndex Wire index (`0` = high bit / MSB, `1` = low bit).
257+
* @return The `4x4` embedded unitary.
258+
*/
259+
[[nodiscard]] Matrix4x4 embedInTwoQubit(std::size_t qubitIndex) const;
218260
};
219261

220262
/**
@@ -322,6 +364,12 @@ struct Matrix4x4 {
322364
*/
323365
[[nodiscard]] Matrix4x4 adjoint() const;
324366

367+
/**
368+
* @brief Returns the (non-conjugate) transpose of this matrix.
369+
* @return Transposed matrix `A^T`.
370+
*/
371+
[[nodiscard]] Matrix4x4 transpose() const;
372+
325373
/**
326374
* @brief Returns the trace of this matrix.
327375
* @return Sum of diagonal entries.
@@ -334,6 +382,81 @@ struct Matrix4x4 {
334382
*/
335383
[[nodiscard]] Complex determinant() const;
336384

385+
/**
386+
* @brief Checks whether this matrix is approximately the identity.
387+
* @param tol Maximum allowed complex modulus of each entry difference.
388+
* @return True if every entry is within @p tol of the identity.
389+
*/
390+
[[nodiscard]] bool isIdentity(double tol = MATRIX_TOLERANCE) const;
391+
392+
/**
393+
* @brief Returns the four diagonal entries `(m00, m11, m22, m33)`.
394+
* @return Array of diagonal entries.
395+
*/
396+
[[nodiscard]] std::array<Complex, K_ROWS> diagonal() const;
397+
398+
/**
399+
* @brief Builds a diagonal matrix from four diagonal entries.
400+
* @param diagonalEntries Diagonal entries `(m00, m11, m22, m33)`; must have
401+
* length `K_ROWS`.
402+
* @return Diagonal matrix with the given entries.
403+
*/
404+
[[nodiscard]] static Matrix4x4
405+
fromDiagonal(ArrayRef<Complex> diagonalEntries);
406+
407+
/**
408+
* @brief Kronecker product `lhs (x) rhs` of two single-qubit matrices.
409+
*
410+
* Uses the computational-basis bit order where the first operand labels the
411+
* high bit, matching `UnitaryOpInterface::getUnitaryMatrix4x4`.
412+
*
413+
* @param lhs Left factor (acts on the high bit / qubit 0).
414+
* @param rhs Right factor (acts on the low bit / qubit 1).
415+
* @return The `4x4` Kronecker product.
416+
*/
417+
[[nodiscard]] static Matrix4x4 kron(const Matrix2x2& lhs,
418+
const Matrix2x2& rhs);
419+
420+
/**
421+
* @brief Returns the entries of column @p col, top to bottom.
422+
* @param col Column index in `[0, K_COLS)`.
423+
* @return Array of the four column entries.
424+
*/
425+
[[nodiscard]] std::array<Complex, K_ROWS> column(std::size_t col) const;
426+
427+
/**
428+
* @brief Overwrites column @p col with @p values.
429+
* @param col Column index in `[0, K_COLS)`.
430+
* @param values New column entries, top to bottom; must have length `K_ROWS`.
431+
*/
432+
void setColumn(std::size_t col, ArrayRef<Complex> values);
433+
434+
/**
435+
* @brief Returns the entries of row @p row, left to right.
436+
* @param row Row index in `[0, K_ROWS)`.
437+
* @return View over the four row entries.
438+
*/
439+
[[nodiscard]] ArrayRef<const Complex> row(std::size_t row) const;
440+
441+
/**
442+
* @brief Overwrites row @p row with @p values.
443+
* @param row Row index in `[0, K_ROWS)`.
444+
* @param values New row entries, left to right; must have length `K_COLS`.
445+
*/
446+
void setRow(std::size_t row, ArrayRef<Complex> values);
447+
448+
/**
449+
* @brief Returns the element-wise real parts in row-major order.
450+
* @return Real parts of all entries.
451+
*/
452+
[[nodiscard]] std::array<double, K_SIZE_AT_COMPILE_TIME> realPart() const;
453+
454+
/**
455+
* @brief Returns the element-wise imaginary parts in row-major order.
456+
* @return Imaginary parts of all entries.
457+
*/
458+
[[nodiscard]] std::array<double, K_SIZE_AT_COMPILE_TIME> imagPart() const;
459+
337460
/**
338461
* @brief Checks approximate equality using an absolute entry-wise tolerance.
339462
*
@@ -354,6 +477,58 @@ struct Matrix4x4 {
354477
* @return `true` when @p src is 4x4.
355478
*/
356479
[[nodiscard]] bool assignFrom(const DynamicMatrix& src);
480+
481+
/**
482+
* @brief Embed this two-qubit matrix into an @p numQubits-qubit Hilbert
483+
* space.
484+
*
485+
* Operand 0 labels the high bit of the pair and acts on @p q0Index; operand 1
486+
* labels the low bit and acts on @p q1Index. For each basis pair whose other
487+
* wires match, copies this matrix at the packed two-qubit row/column indices.
488+
*
489+
* @param numQubits Number of qubits in the target Hilbert space.
490+
* @param q0Index Wire index of operand 0.
491+
* @param q1Index Wire index of operand 1.
492+
* @return Embedded unitary as a dynamic matrix.
493+
*/
494+
[[nodiscard]] DynamicMatrix embedInNqubit(std::size_t numQubits,
495+
std::size_t q0Index,
496+
std::size_t q1Index) const;
497+
498+
/**
499+
* @brief Reorder this matrix to act on qubits `{0, 1}`.
500+
*
501+
* @param q0Index Wire index of operand 0; @p q1Index wire index of operand 1.
502+
* @return Reordered copy of this matrix.
503+
*/
504+
[[nodiscard]] Matrix4x4 reorderForQubits(std::size_t q0Index,
505+
std::size_t q1Index) const;
506+
507+
/**
508+
* @brief Computes the eigendecomposition of this real symmetric matrix.
509+
*
510+
* @copydoc Matrix4x4::symmetricEigen4(const std::array<double, 16>&)
511+
*
512+
* @pre Entries are real (imaginary parts must be negligible). The real parts
513+
* must form a symmetric matrix; imaginary parts are ignored.
514+
*/
515+
[[nodiscard]] SymmetricEigen4 symmetricEigen4() const;
516+
517+
/**
518+
* @brief Computes the eigendecomposition of a real symmetric `4x4` matrix.
519+
*
520+
* Uses Householder tridiagonalization (EISPACK `tred2`) followed by implicit
521+
* QL iteration (`tql2`) on the tridiagonal form.
522+
*
523+
* @pre @p symmetric is real and symmetric: `symmetric[i,j] == symmetric[j,i]`
524+
* for all `i, j`. Only the lower triangle (including the diagonal) is read,
525+
* but supplying a non-symmetric matrix yields undefined numerical results.
526+
*
527+
* @param symmetric Row-major real symmetric `4x4` matrix.
528+
* @return Ascending eigenvalues and matching eigenvectors (as columns).
529+
*/
530+
[[nodiscard]] static SymmetricEigen4
531+
symmetricEigen4(const std::array<double, 16>& symmetric);
357532
};
358533

359534
/**
@@ -539,6 +714,41 @@ class DynamicMatrix {
539714
[[nodiscard]] bool isApprox(const DynamicMatrix& other,
540715
double tol = MATRIX_TOLERANCE) const;
541716

717+
/**
718+
* @brief Returns the trace of this matrix.
719+
* @return Sum of diagonal entries.
720+
*/
721+
[[nodiscard]] Complex trace() const;
722+
723+
/**
724+
* @brief Matrix product `*this * rhs`.
725+
* @param rhs Right-hand factor.
726+
* @return Product of the two matrices.
727+
*/
728+
[[nodiscard]] DynamicMatrix operator*(const DynamicMatrix& rhs) const;
729+
730+
/**
731+
* @brief Element-wise scaling by a complex scalar.
732+
* @param scalar Factor applied to every matrix entry.
733+
* @return Scaled copy of this matrix.
734+
*/
735+
[[nodiscard]] DynamicMatrix operator*(const Complex& scalar) const;
736+
737+
/**
738+
* @brief Element-wise in-place scaling by a complex scalar.
739+
* @param scalar Factor applied to every matrix entry.
740+
* @return Reference to this matrix.
741+
*/
742+
DynamicMatrix& operator*=(const Complex& scalar);
743+
744+
/**
745+
* @brief Checks whether this matrix is approximately the identity.
746+
* @param tol Maximum allowed complex modulus of each off-diagonal entry and
747+
* each diagonal deviation from one.
748+
* @return True when the matrix is close to the identity.
749+
*/
750+
[[nodiscard]] bool isIdentity(double tol = MATRIX_TOLERANCE) const;
751+
542752
private:
543753
struct Impl;
544754
std::unique_ptr<Impl> impl_;
@@ -558,4 +768,31 @@ inline constexpr bool
558768
std::disjunction_v<std::is_same<T, Matrix1x1>, std::is_same<T, Matrix2x2>,
559769
std::is_same<T, Matrix4x4>,
560770
std::is_same<T, DynamicMatrix>>;
771+
772+
/// Scalar-on-the-left multiply `scalar * matrix` (commutes with the member
773+
/// `matrix * scalar`). Provided so generic code can scale a matrix from
774+
/// either side.
775+
[[nodiscard]] Matrix2x2 operator*(const Complex& scalar,
776+
const Matrix2x2& matrix);
777+
/// @copydoc operator*(const Complex&, const Matrix2x2&)
778+
[[nodiscard]] Matrix4x4 operator*(const Complex& scalar,
779+
const Matrix4x4& matrix);
780+
/// @copydoc operator*(const Complex&, const Matrix2x2&)
781+
[[nodiscard]] DynamicMatrix operator*(const Complex& scalar,
782+
const DynamicMatrix& matrix);
783+
784+
/**
785+
* @brief Eigenvalues and eigenvectors of a real symmetric `4x4` matrix.
786+
*
787+
* `eigenvalues` are sorted ascending and `eigenvectors` holds the
788+
* corresponding orthonormal eigenvectors as columns (column `j` is the
789+
* eigenvector for `eigenvalues[j]`).
790+
*/
791+
struct SymmetricEigen4 {
792+
/// Eigenvalues in ascending order.
793+
std::array<double, 4> eigenvalues{};
794+
/// Orthonormal eigenvectors as columns (column `j` matches `eigenvalues[j]`).
795+
Matrix4x4 eigenvectors{};
796+
};
797+
561798
} // namespace mlir::qco

0 commit comments

Comments
 (0)