Hyperiso 1.0.3
Modular flavour-physics calculations, Wilson coefficients and statistical inference
Loading...
Searching...
No Matches
RealMatrix.py
Go to the documentation of this file.
1"""Dense real-matrix utilities backed by the C++ ``RealMatrix`` type.
2
3This module provides a Python-friendly wrapper around the C++ dense matrix
4implementation used throughout the statistics and copula stack. Inputs and
5outputs are converted to native Python containers, while heavy linear-algebra
6operations are delegated to the C++ backend.
7"""
8
9from __future__ import annotations
10
11from dataclasses import dataclass
12from typing import List, Optional, Sequence, Tuple, Union
13
14from pyhyperiso.phyperiso.pyhyperiso import math as ma
15
16
17Number = Union[int, float]
18NestedList = Sequence[Sequence[Number]]
19
20
21@dataclass(frozen=True)
23 """Signed logarithmic determinant.
24
25 Attributes:
26 logdet: Natural logarithm of the determinant absolute value.
27 sign: Determinant sign, usually ``-1``, ``0``, or ``1``.
28 """
29
30 logdet: float
31 sign: int
32
33
34class Matrix:
35 """Python wrapper for the C++ ``RealMatrix`` class.
36
37 The wrapper accepts common Python matrix representations and exposes Python
38 arithmetic operators. Matrix-matrix multiplication is also mapped to the
39 ``@`` operator.
40
41 Args:
42 data: Optional nested sequence representing a dense matrix.
43 rows: Number of rows for an empty matrix or flat initialization.
44 cols: Number of columns for an empty matrix or flat initialization.
45 flat: Optional row-major flat values. Requires ``rows`` and ``cols``.
46
47 Raises:
48 ValueError: If constructor arguments are incomplete or inconsistent.
49
50 Example:
51 >>> A = Matrix([[1.0, 2.0], [3.0, 4.0]])
52 >>> I = eye(2)
53 >>> (A @ I).to_list()
54 [[1.0, 2.0], [3.0, 4.0]]
55 """
56
57 __slots__ = ("_cpp_obj",)
58
60 self,
61 data: Optional[NestedList] = None,
62 *,
63 rows: Optional[int] = None,
64 cols: Optional[int] = None,
65 flat: Optional[Sequence[Number]] = None,
66 ):
67 if data is not None:
68 self._cpp_obj = ma.matrix.RealMatrix([[float(x) for x in row] for row in data])
69 return
70
71 if flat is not None:
72 if rows is None or cols is None:
73 raise ValueError("When using flat=..., you must also provide rows=... and cols=...")
74 self._cpp_obj = ma.matrix.RealMatrix([float(x) for x in flat], int(rows), int(cols))
75 return
76
77 if rows is not None or cols is not None:
78 if rows is None or cols is None:
79 raise ValueError("Provide both rows and cols.")
80 self._cpp_obj = ma.matrix.RealMatrix(int(rows), int(cols))
81 return
82
83 self._cpp_obj = ma.matrix.RealMatrix()
84
85 @classmethod
86 def _from_cpp(cls, cpp_obj) -> "Matrix":
87 """Wrap an existing C++ ``RealMatrix`` object.
88
89 Args:
90 cpp_obj: Bound C++ matrix object.
91
92 Returns:
93 Matrix: Python wrapper sharing the provided C++ object.
94 """
95 inst = cls.__new__(cls)
96 inst._cpp_obj = cpp_obj
97 return inst
98
99 @classmethod
100 def from_cpp(cls, cpp_obj) -> "Matrix":
101 """Public alias for wrapping a C++ ``RealMatrix`` object."""
102 return cls._from_cpp(cpp_obj)
103
104 @property
105 def rows(self) -> int:
106 """Number of matrix rows."""
107 return int(self._cpp_obj.rows)
108
109 @property
110 def cols(self) -> int:
111 """Number of matrix columns."""
112 return int(self._cpp_obj.cols)
113
114 @property
115 def shape(self) -> Tuple[int, int]:
116 """Matrix shape as ``(rows, cols)``."""
117 r, c = self._cpp_obj.shape
118 return int(r), int(c)
119
120 def to_list(self) -> List[List[float]]:
121 """Return a nested Python list copy of the matrix.
122
123 Returns:
124 list[list[float]]: Row-major matrix values.
125 """
126 return self._cpp_obj.to_list()
127
128 def __repr__(self) -> str:
129 """Return a compact representation containing the matrix shape."""
130 r, c = self.shape
131 return f"Matrix(shape=({r}, {c}))"
132
133 def __getitem__(self, idx: Tuple[int, int]) -> float:
134 """Return one matrix entry using ``matrix[i, j]`` indexing."""
135 i, j = idx
136 return float(self._cpp_obj[(int(i), int(j))])
137
138 def __setitem__(self, idx: Tuple[int, int], value: Number) -> None:
139 """Set one matrix entry using ``matrix[i, j] = value`` indexing."""
140 i, j = idx
141 self._cpp_obj[(int(i), int(j))] = float(value)
142
143 def is_symmetric(self) -> bool:
144 """Return whether the C++ backend considers the matrix symmetric."""
145 return bool(self._cpp_obj.is_symmetric())
146
147 def T(self) -> "Matrix":
148 """Return the transpose of the matrix.
149
150 Returns:
151 Matrix: Transposed matrix.
152 """
153 return Matrix._from_cpp(self._cpp_obj.transpose())
154
155 def inv(self) -> "Matrix":
156 """Return the matrix inverse.
157
158 Returns:
159 Matrix: Inverse matrix computed by C++.
160
161 Raises:
162 RuntimeError: Propagated from the C++ backend if inversion fails.
163 """
164 return Matrix._from_cpp(self._cpp_obj.inv())
165
166 def slogdet(self) -> SignedLogDet:
167 """Compute the signed logarithmic determinant.
168
169 Returns:
170 SignedLogDet: Determinant sign and log-absolute determinant.
171 """
172 s = self._cpp_obj.slogdet()
173 return SignedLogDet(logdet=float(s.logdet), sign=int(s.sign))
174
175 def eig(self) -> Tuple["Matrix", "Matrix"]:
176 """Compute the eigensystem of the matrix.
177
178 Returns:
179 tuple[Matrix, Matrix]: ``(D, P)``, where ``D`` is a diagonal matrix
180 of eigenvalues and ``P`` contains eigenvectors as returned by C++.
181 """
182 es = self._cpp_obj.eig()
183 return Matrix._from_cpp(es.D), Matrix._from_cpp(es.P)
184
185 def to_cpp(self):
186 """Return the wrapped C++ matrix object."""
187 return self._cpp_obj
188
189 @staticmethod
190 def _as_cpp(other: Union["Matrix", NestedList]):
191 """Convert a Python matrix-like object to a C++ ``RealMatrix``."""
192 if isinstance(other, Matrix):
193 return other._cpp_obj
194 return ma.matrix.RealMatrix([[float(x) for x in row] for row in other])
195
196 def __neg__(self) -> "Matrix":
197 """Return ``-self``."""
198 return Matrix._from_cpp(-self._cpp_obj)
199
200 def __add__(self, other: Union["Matrix", NestedList]) -> "Matrix":
201 """Return matrix addition with another matrix-like object."""
202 return Matrix._from_cpp(self._cpp_obj + self._as_cpp(other))
203
204 def __sub__(self, other: Union["Matrix", NestedList]) -> "Matrix":
205 """Return matrix subtraction with another matrix-like object."""
206 return Matrix._from_cpp(self._cpp_obj - self._as_cpp(other))
207
208 def __mul__(self, other: Union["Matrix", NestedList, Number]) -> "Matrix":
209 """Return C++ multiplication by a scalar or matrix-like object."""
210 if isinstance(other, (int, float)):
211 return Matrix._from_cpp(self._cpp_obj * float(other))
212 return Matrix._from_cpp(self._cpp_obj * self._as_cpp(other))
213
214 def __rmul__(self, other: Number) -> "Matrix":
215 """Return scalar multiplication from the left."""
216 if not isinstance(other, (int, float)):
217 return NotImplemented
218 return Matrix._from_cpp(float(other) * self._cpp_obj)
219
220 def __truediv__(self, other: Number) -> "Matrix":
221 """Return division by a scalar."""
222 if not isinstance(other, (int, float)):
223 return NotImplemented
224 return Matrix._from_cpp(self._cpp_obj / float(other))
225
226 def __matmul__(self, other: Union["Matrix", NestedList]) -> "Matrix":
227 """Return matrix multiplication using the Python ``@`` operator."""
228 return Matrix._from_cpp(self._cpp_obj * self._as_cpp(other))
229
230
231def eye(n: int) -> Matrix:
232 """Create an identity matrix.
233
234 Args:
235 n: Matrix dimension.
236
237 Returns:
238 Matrix: ``n x n`` identity matrix.
239 """
240 return Matrix._from_cpp(ma.matrix.eye(int(n)))
241
242
243def nearest_psd(R: Union[Matrix, NestedList], thr: float = 1e-12) -> Matrix:
244 """Project a matrix to the nearest positive semidefinite matrix.
245
246 This helper is used by copula construction to regularize correlation
247 matrices before decomposition.
248
249 Args:
250 R: Input matrix or nested list.
251 thr: Numerical threshold used by the C++ routine.
252
253 Returns:
254 Matrix: Regularized positive-semidefinite matrix.
255 """
256 cpp_R = (
257 R._cpp_obj
258 if isinstance(R, Matrix)
259 else ma.matrix.RealMatrix([[float(x) for x in row] for row in R])
260 )
261 return Matrix._from_cpp(ma.matrix.nearest_psd(cpp_R, float(thr)))
262
263
264def cholesky_L(R: Union[Matrix, NestedList]) -> Matrix:
265 """Compute the lower Cholesky factor of a matrix.
266
267 Args:
268 R: Input symmetric positive-semidefinite matrix.
269
270 Returns:
271 Matrix: Lower triangular Cholesky factor as returned by C++.
272 """
273 cpp_R = (
274 R._cpp_obj
275 if isinstance(R, Matrix)
276 else ma.matrix.RealMatrix([[float(x) for x in row] for row in R])
277 )
278 return Matrix._from_cpp(ma.matrix.cholesky_L(cpp_R))
279
280
281__all__ = ["Matrix", "SignedLogDet", "eye", "nearest_psd", "cholesky_L"]
Tuple["Matrix", "Matrix"] eig(self)
"Matrix" __add__(self, Union["Matrix", NestedList] other)
"Matrix" __sub__(self, Union["Matrix", NestedList] other)
Tuple[int, int] shape(self)
"Matrix" __rmul__(self, Number other)
"Matrix" __neg__(self)
"Matrix" T(self)
List[List[float]] to_list(self)
None __setitem__(self, Tuple[int, int] idx, Number value)
"Matrix" __truediv__(self, Number other)
"Matrix" __mul__(self, Union["Matrix", NestedList, Number] other)
"Matrix" from_cpp(cls, cpp_obj)
__init__(self, Optional[NestedList] data=None, *Optional[int] rows=None, Optional[int] cols=None, Optional[Sequence[Number]] flat=None)
Definition RealMatrix.py:66
"Matrix" _from_cpp(cls, cpp_obj)
Definition RealMatrix.py:86
"Matrix" inv(self)
"Matrix" __matmul__(self, Union["Matrix", NestedList] other)
SignedLogDet slogdet(self)
str __repr__(self)
float __getitem__(self, Tuple[int, int] idx)
_as_cpp(Union["Matrix", NestedList] other)
bool is_symmetric(self)
Matrix cholesky_L(Union[Matrix, NestedList] R)
Matrix eye(int n)
Matrix nearest_psd(Union[Matrix, NestedList] R, float thr=1e-12)