Skip to content

Commit 2daea51

Browse files
Feature/repeated maps (#41)
* default data_cov for updated distribution * more efficient for repeated measurements * type handling for std_list * whitespace / indent * Update src/mud/util.py * add unit tests for transform_linear_map * Update src/mud/util.py * testing * more coverage * readd test deleted by accident
1 parent 13946c8 commit 2daea51

File tree

2 files changed

+74
-14
lines changed

2 files changed

+74
-14
lines changed

src/mud/funs.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ def mud_sol(A, b, y=None,
172172
return mud_point
173173

174174

175-
def updated_cov(X, init_cov, data_cov):
175+
def updated_cov(X, init_cov=None, data_cov=None):
176176
"""
177177
We start with the posterior covariance from ridge regression
178178
Our matrix R = init_cov^(-1) - X.T @ pred_cov^(-1) @ X
@@ -186,8 +186,31 @@ def updated_cov(X, init_cov, data_cov):
186186
np.linalg.inv(init_cov) )
187187
188188
We return the updated covariance using a form of it derived
189-
which applies Hua's identity in order to use Woodbury's identity
189+
which applies Hua's identity in order to use Woodbury's identity.
190+
191+
>>> updated_cov(np.eye(2))
192+
array([[1., 0.],
193+
[0., 1.]])
194+
>>> updated_cov(np.eye(2)*2)
195+
array([[0.25, 0. ],
196+
[0. , 0.25]])
197+
>>> updated_cov(np.eye(3)[:, :2]*2, data_cov=np.eye(3))
198+
array([[0.25, 0. ],
199+
[0. , 0.25]])
200+
>>> updated_cov(np.eye(3)[:, :2]*2, init_cov=np.eye(2))
201+
array([[0.25, 0. ],
202+
[0. , 0.25]])
190203
"""
204+
if init_cov is None:
205+
init_cov = np.eye(X.shape[1])
206+
else:
207+
assert X.shape[1] == init_cov.shape[1]
208+
209+
if data_cov is None:
210+
data_cov = np.eye(X.shape[0])
211+
else:
212+
assert X.shape[0] == data_cov.shape[1]
213+
191214
pred_cov = X @ init_cov @ X.T
192215
inv_pred_cov = np.linalg.pinv(pred_cov)
193216
# pinv b/c inv unstable for rank-deficient A

src/mud/util.py

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,61 @@ def std_from_equipment(tolerance=0.1, probability=0.95):
1616
def transform_linear_map(operator, data, std):
1717
"""
1818
Takes a linear map `operator` of size (len(data), dim_input)
19+
or (1, dim_input) for repeated observations, along with
20+
a vector `data` representing observations. It is assumed
21+
that `data` is formed with `M@truth + sigma` where `sigma ~ N(0, std)`
22+
23+
This then transforms it to the MWE form expected by the DCI framework.
24+
It returns a matrix `A` of shape (1, dim_input) and np.float `b`
1925
and transforms it to the MWE form expected by the DCI framework.
26+
27+
>>> X = np.ones((10, 2))
28+
>>> x = np.array([0.5, 0.5]).reshape(-1, 1)
29+
>>> std = 1
30+
>>> d = X @ x
31+
>>> A, b = transform_linear_map(X, d, std)
32+
>>> np.linalg.norm(A @ x + b)
33+
0.0
34+
>>> A, b = transform_linear_map(X, d, [std]*10)
35+
>>> np.linalg.norm(A @ x + b)
36+
0.0
37+
>>> A, b = transform_linear_map(np.array([[1, 1]]), d, std)
38+
>>> np.linalg.norm(A @ x + b)
39+
0.0
40+
>>> A, b = transform_linear_map(np.array([[1, 1]]), d, [std]*10)
41+
Traceback (most recent call last):
42+
...
43+
ValueError: For repeated measurements, pass a float for std
2044
"""
21-
num_observations = len(data)
22-
assert operator.shape[0] == num_observations, "Operator shape mismatch"
23-
if isinstance(std, int) or isinstance(std, float):
24-
std = np.array([std] * num_observations)
25-
if isinstance(std, list) or isinstance(std, tuple):
26-
std = np.array(std)
2745
if isinstance(data, np.ndarray):
28-
data = list(data.ravel())
29-
assert len(std) == num_observations, "Standard deviation shape mismatch"
30-
D = np.diag(1.0 / (std * np.sqrt(num_observations)))
31-
A = np.sum(D @ operator, axis=0)
32-
b = np.sum(np.divide(data, std))
33-
return A, (-1.0 / np.sqrt(num_observations)) * b.reshape(-1, 1)
46+
data = data.ravel()
47+
48+
num_observations = len(data)
49+
50+
if operator.shape[0] > 1: # if not repeated observations
51+
assert operator.shape[0] == num_observations, \
52+
f"Operator shape mismatch, op={operator.shape}, obs={num_observations}"
53+
if isinstance(std, (float, int)):
54+
std = np.array([std] * num_observations)
55+
if isinstance(std, (list, tuple)):
56+
std = np.array(std)
57+
assert len(std) == num_observations, "Standard deviation shape mismatch"
58+
assert 0 not in np.round(std, 14), "Std must be > 1E-14"
59+
D = np.diag(1.0 / (std * np.sqrt(num_observations)))
60+
A = np.sum(D @ operator, axis=0)
61+
else:
62+
if isinstance(std, (list, tuple, np.ndarray)):
63+
raise ValueError("For repeated measurements, pass a float for std")
64+
assert std > 1E-14, "Std must be > 1E-14"
65+
A = np.sqrt(num_observations) / std * operator
66+
67+
b = -1.0 / np.sqrt(num_observations) * np.sum(np.divide(data, std))
68+
return A, b
3469

3570

3671
def transform_linear_setup(operator_list, data_list, std_list):
72+
if isinstance(std_list, (float, int)):
73+
std_list = [std_list] * len(data_list)
3774
# repeat process for multiple quantities of interest
3875
results = [transform_linear_map(o, d, s) for
3976
o, d, s in zip(operator_list, data_list, std_list)]

0 commit comments

Comments
 (0)