Skip to content

Commit 9b09ea6

Browse files
committed
Fix diagonal estimate
1 parent dcaa678 commit 9b09ea6

File tree

3 files changed

+395
-200
lines changed

3 files changed

+395
-200
lines changed

examples/07_example_negative_log_likelihood.ipynb

+225-161
Large diffs are not rendered by default.

sportran/current/current.py

+35-12
Original file line numberDiff line numberDiff line change
@@ -405,14 +405,15 @@ def maxlike_estimate(self,
405405
likelihood = 'wishart',
406406
solver = 'BFGS',
407407
guess_runave_window = 50,
408+
minimize_kwargs={}
408409
):
409410

410411
if likelihood.lower() == 'wishart':
411-
data = self.cospectrum.real # TODO figure out this business of real vs abs value
412+
data = self.cospectrum.real * self.N_CURRENTS
412413
elif likelihood.lower() == 'chisquare' or likelihood.lower() == 'chisquared':
413-
data = self.psd
414+
data = self.psd * self.N_CURRENTS
414415
elif likelihood == 'variancegamma' or likelihood == 'variance-gamma':
415-
data = self.cospectrum.real[0,1]
416+
data = self.cospectrum.real[0,1] # * self.N_CURRENTS
416417
else:
417418
raise ValueError("Likelihood must be Wishart, Chi-square, or Variance-Gamma.")
418419

@@ -429,7 +430,8 @@ def maxlike_estimate(self,
429430
# Minimize the negative log-likelihood with a fixed number of parameters
430431
self.maxlike.maxlike(mask = mask,
431432
guess_runave_window = guess_runave_window,
432-
n_parameters = n_parameters)
433+
n_parameters = n_parameters,
434+
minimize_kwargs=minimize_kwargs)
433435

434436
elif isinstance(n_parameters, list) or isinstance(n_parameters, np.ndarray):
435437
do_AIC = True
@@ -452,7 +454,8 @@ def maxlike_estimate(self,
452454
guess_runave_window = guess_runave_window,
453455
n_parameters = int(n_par),
454456
omega_fixed = None,
455-
write_log = False)
457+
write_log = False,
458+
minimize_kwargs=minimize_kwargs)
456459

457460
_aic.append(self.maxlike.log_likelihood_value - n_par)
458461
_filters.append(deepcopy(self.maxlike))
@@ -466,21 +469,41 @@ def maxlike_estimate(self,
466469
params = self.maxlike.parameters_mean
467470
params_std = self.maxlike.parameters_std
468471

472+
om = self.maxlike.omega
469473
if likelihood == 'wishart':
470-
self.NLL_spline = lambda x: np.einsum('wba,wbc->wac', *(lambda y: (y, y))(model(omega_fixed, params)(x)))
474+
# nw = params.shape[0]//2
475+
# re_NLL_spline = lambda x: np.einsum('wba,wbc->wac', *(lambda y: (y, y))(model(omega_fixed, params[:nw])(x)))
476+
# im_NLL_spline = lambda x: np.einsum('wba,wbc->wac', *(lambda y: (y, y))(model(omega_fixed, params[nw:])(x)))
477+
# self.NLL_mean = re_NLL_spline(om) + 1j*im_NLL_spline(om)
478+
# _spl = lambda x: np.einsum('wba,wbc->wac', *(lambda y: (y, y))(model(omega_fixed, params)(x)))
479+
# self.NLL_mean = _spl(om)
480+
from sportran.md.maxlike import scale_matrix
481+
self.NLL_mean = scale_matrix(model, params, om, omega_fixed, self.N_CURRENTS) * self.N_EQUIV_COMPONENTS / self.N_CURRENTS
471482
try:
472-
self.NLL_spline_upper = lambda x: np.einsum('wba,wbc->wac', *(lambda y: (y, y))(model(omega_fixed, params + params_std)(x)))
473-
self.NLL_spline_lower = lambda x: np.einsum('wba,wbc->wac', *(lambda y: (y, y))(model(omega_fixed, params - params_std)(x)))
483+
# _NLL_spline_upper = lambda x: np.einsum('wba,wbc->wac', *(lambda y: (y, y))(model(omega_fixed, params + params_std)(x)))
484+
# _NLL_spline_lower = lambda x: np.einsum('wba,wbc->wac', *(lambda y: (y, y))(model(omega_fixed, params - params_std)(x)))
485+
# re_NLL_spline_upper = lambda x: np.einsum('wba,wbc->wac', *(lambda y: (y, y))(model(omega_fixed, params[:nw] + params_std[:nw])(x)))
486+
# re_NLL_spline_lower = lambda x: np.einsum('wba,wbc->wac', *(lambda y: (y, y))(model(omega_fixed, params[:nw] - params_std[:nw])(x)))
487+
# im_NLL_spline_upper = lambda x: np.einsum('wba,wbc->wac', *(lambda y: (y, y))(model(omega_fixed, params[nw:] + params_std[nw:])(x)))
488+
# im_NLL_spline_lower = lambda x: np.einsum('wba,wbc->wac', *(lambda y: (y, y))(model(omega_fixed, params[nw:] - params_std[nw:])(x)))
489+
# self.NLL_upper = re_NLL_spline_upper(om) + 1j*im_NLL_spline_upper(om)
490+
# self.NLL_lower = re_NLL_spline_lower(om) + 1j*im_NLL_spline_lower(om)
491+
self.NLL_upper = scale_matrix(model, params+params_std, om, omega_fixed, self.N_CURRENTS) * self.N_EQUIV_COMPONENTS / self.N_CURRENTS
492+
self.NLL_lower = scale_matrix(model, params-params_std, om, omega_fixed, self.N_CURRENTS) * self.N_EQUIV_COMPONENTS / self.N_CURRENTS
474493
except TypeError:
475494
pass
476495
else:
477-
self.NLL_spline = model(omega_fixed, params)
496+
_NLL_spline = model(omega_fixed, params)
497+
fact = np.sqrt(self.N_EQUIV_COMPONENTS) / self.N_CURRENTS / np.sqrt(2)
498+
self.NLL_mean = _NLL_spline(om) * fact
478499
try:
479-
self.NLL_spline_upper = model(omega_fixed, params + params_std)
480-
self.NLL_spline_lower = model(omega_fixed, params - params_std)
500+
_NLL_spline_upper = model(omega_fixed, params + params_std)
501+
_NLL_spline_lower = model(omega_fixed, params - params_std)
502+
self.NLL_upper = _NLL_spline_upper(om) * fact
503+
self.NLL_lower = _NLL_spline_lower(om) * fact
481504
except TypeError:
482505
pass
483-
506+
484507
# self.estimate = self.maxlike.parameters_mean[0]*self.maxlike.factor
485508

486509
# try:

sportran/md/maxlike.py

+135-27
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,19 @@ def __repr__(self):
8686
msg = 'MaxLikeFilter:\n'
8787
return msg
8888

89-
def maxlike(self, data=None, model=None, n_parameters=None, likelihood=None, solver=None, mask=None, n_components=None, n_currents=None, guess_runave_window=50, omega_fixed=None, write_log=True):
89+
def maxlike(self,
90+
data=None,
91+
model=None,
92+
n_parameters=None,
93+
likelihood=None,
94+
solver=None,
95+
mask=None,
96+
n_components=None,
97+
n_currents=None,
98+
guess_runave_window=50,
99+
omega_fixed=None,
100+
write_log=True,
101+
minimize_kwargs=None):
90102
"""
91103
Perform the maximum-likelihood estimation.
92104
"""
@@ -160,10 +172,13 @@ def maxlike(self, data=None, model=None, n_parameters=None, likelihood=None, sol
160172

161173
# Minimize the negative log-likelihood
162174
self._guess_data = guess_data
175+
guess_par = guess_data #normalize_parameters(guess_data, guess_data)
176+
# print(guess_data - denormalize_parameters(guess_par, guess_data))
163177
res = minimize(fun = self.log_like,
164-
x0 = guess_data,
178+
x0 = guess_par,
165179
args = (self.model, omega, self.omega_fixed, self.data, nu, ell),
166-
method = self.solver)
180+
method = self.solver,
181+
**minimize_kwargs)
167182

168183
# Covariance of the parameters
169184
try:
@@ -176,6 +191,7 @@ def maxlike(self, data=None, model=None, n_parameters=None, likelihood=None, sol
176191
cov = None
177192

178193
self.parameters_mean = res.x
194+
# self.parameters_mean = denormalize_parameters(res.x, self._guess_data)
179195
if cov is not None:
180196
self.parameters_std = np.sqrt(cov.diagonal())
181197

@@ -199,56 +215,110 @@ def guess_data(self, data, omega, omega_fixed, ell, nu, window=10, loglike='wish
199215

200216
guess_data = np.array([guess_data[..., j] for j in [np.argmin(np.abs(omega - omega_fixed[i])) for i in range(len(omega_fixed))]])
201217
print(guess_data.shape)
202-
203218
if loglike == 'wishart':
204-
guess_data = np.array([cholesky(g, lower=False) for g in guess_data]) / np.sqrt(ell)
205-
219+
guess_data = np.array([cholesky(g, lower=False) for g in guess_data]) #/ np.sqrt(ell)
206220
upper_triangle_indices = np.triu_indices(nu)
207-
guess_data = guess_data[:, upper_triangle_indices[0], upper_triangle_indices[1]].T.reshape(-1)
208221

209-
# guess_data = np.array([guess_data[:, 0, 0], guess_data[:, 0, 1], guess_data[:, 1, 1]]).reshape(-1)
210-
222+
nw = omega_fixed.shape[0]
223+
ie = 0
224+
if guess_data.dtype == np.complex128:
225+
guess_params = np.zeros((nw, nu**2))
226+
for i, j in zip(*upper_triangle_indices):
227+
if i == j:
228+
guess_params[:, ie] = guess_data[:, i, j].real
229+
ie += 1
230+
else:
231+
guess_params[:, ie] = guess_data[:, i, j].real
232+
ie += 1
233+
guess_params[:, ie] = guess_data[:, i, j].imag
234+
ie += 1
235+
guess_data = guess_params.flatten()
236+
else:
237+
guess_params = np.zeros((nw, nu*(nu+1)//2))
238+
for i, j in zip(*upper_triangle_indices):
239+
guess_params[:, ie] = guess_data[:, i, j]
240+
ie += 1
241+
guess_data = guess_params.flatten()
242+
211243
return guess_data
212-
213-
def log_likelihood_wishart(self, w, model, omega, omega_fixed, data_, nu, ell, eps = 1e-9):
244+
245+
def log_likelihood_wishart(self, w, model, omega, omega_fixed, data_, nu, ell, eps = 1e-3):
214246
"""
215247
Logarithm of the Wishart probability density function.
216248
"""
217249
n = ell
218250
p = nu
219251

220252
# Compute scale matrix from the model (symmetrize to ensure positive definiteness)
221-
spline = model(omega_fixed, w)
222-
V = spline(omega)
223-
V = opt_einsum.contract('wba,wbc->wac', V, V) / n
224-
253+
V = scale_matrix(model, w, omega, omega_fixed, p)
225254
X = data_
255+
226256
if n < p:
227-
# Singular Wishart
228-
multig = multigammaln(0.5 * n, n)
229-
S = np.linalg.svd(X, hermitian = True, compute_uv = False)
257+
S = np.linalg.svd(X, hermitian=True, compute_uv=False)
230258
detX = np.array([np.prod(s[abs(s) > eps]) for s in S])
231259

232260
else:
233-
multig = multigammaln(0.5 * n, p)
234261
detX = np.linalg.det(X)
235262

236263
invV = np.linalg.inv(V)
237264
detV = np.linalg.det(V)
238265

239266
trinvV_X = opt_einsum.contract('wab,wba->w', invV, X)
240267

241-
# log_pdf = - (0.5 * (-n * p * LOG2 - n * np.log(detV) + (n - p - 1) * np.log(detX) - trinvV_X) - multig)
242268
coeff_detV = -n
243269
coeff_detX = n-p-1
244-
log_pdf = coeff_detV * np.log(detV) + coeff_detX * np.log(detX) - trinvV_X
245-
# print(-np.sum(log_pdf))
270+
271+
log_pdf = coeff_detV * np.log(detV + eps) + coeff_detX * np.log(detX + eps) - trinvV_X
272+
tot = -np.sum(log_pdf)
273+
return tot
274+
275+
def log_likelihood_complex_wishart(self, w, model, omega, omega_fixed, data_, nu, ell, eps = 1e-9):
276+
"""
277+
Logarithm of the Complex Wishart probability density function.
278+
"""
279+
n = ell
280+
p = nu
281+
282+
# Compute scale matrix from the model (symmetrize to ensure positive definiteness)
283+
S = scale_matrix(model, w, omega, omega_fixed, p)
284+
# nw = w.shape[0]//2
285+
# real_part = model(omega_fixed, w[:nw])
286+
# imag_part = model(omega_fixed, w[nw:])
287+
288+
# # Lower Cholesky factor of S
289+
# L = (real_part(omega) + 1j*imag_part(omega))
290+
# S = opt_einsum.contract('wba,wbc->wac', L.conj(), L)
291+
292+
# The distribution refers to the sample covariance matrix of the (complex) multinormal vectors, not their average
293+
X = data_
294+
295+
if n < p:
296+
raise ValueError('n must be greater or equal than p')
297+
# Singular Wishart
298+
multig = multigammaln(0.5 * n, n)
299+
S = np.linalg.svd(X, hermitian = True, compute_uv = False)
300+
detX = np.array([np.prod(s[abs(s) > eps]) for s in S])
301+
else:
302+
# multig = multigammaln(0.5 * n, p)
303+
logdetX = np.log(np.abs(np.linalg.det(X).real))
304+
305+
invS = np.linalg.inv(S)
306+
logdetS = np.log(np.abs(np.linalg.det(S).real))
307+
308+
trinvS_X = opt_einsum.contract('wab,wba->w', invS, X).real
309+
310+
log_pdf = (n-p)*logdetX - trinvS_X - n*logdetS
311+
# coeff_detV = -n
312+
# coeff_detX = n-p-1
313+
# log_pdf = coeff_detV * np.log(detV) + coeff_detX * np.log(detX) - trinvV_X
314+
# # print(-np.sum(log_pdf))
246315
return -np.sum(log_pdf)
247316

248317
def log_likelihood_offdiag(self, w, model, omega, omega_fixed, data_, nu, ell):
249318
"""
250319
Negative of the logarithm of the Variance-Gamma probability density function.
251320
"""
321+
print(w)
252322
spline = model(omega_fixed, w)
253323
rho = np.clip(spline(omega), -0.98, 0.98)
254324
_alpha = 1 / (1 - rho**2)
@@ -257,12 +327,18 @@ def log_likelihood_offdiag(self, w, model, omega, omega_fixed, data_, nu, ell):
257327
_gamma2 = _alpha**2 - _beta**2
258328
_lambda_minus_half = _lambda - 0.5
259329

260-
# Non sono più sicuro sia sensata questa definizione di z. Non è semplicemtente data_? AH! Forse è la stessa cosa che succede al Chi2, va moltiplicato per il numero di dof. Capire meglio e fare prove.
261-
z = data_ * ell * nu
330+
# Non sono più sicuro sia sensata questa definizione di z.
331+
# Non è semplicemtente data_? AH! Forse è la stessa cosa che succede al Chi2, va moltiplicato per il numero di dof. Capire meglio e fare prove.
332+
z = data_ * nu * ell
262333
absz = np.abs(z)
263-
log_pdf = _lambda * np.log(_gamma2) + _lambda_minus_half * np.log(absz) + np.log(sp.kv(_lambda_minus_half, _alpha * absz)) + \
264-
_beta * z - 0.5 * np.log(np.pi) - np.log(sp.gamma(_lambda)) - _lambda_minus_half * np.log(2 * _alpha)
265-
334+
term1 = _lambda * np.log(_gamma2)
335+
term2 = _lambda_minus_half * np.log(absz)
336+
term3 = np.log(sp.kv(_lambda_minus_half, _alpha * absz))
337+
term4 = _beta * z
338+
term5 = - _lambda_minus_half * np.log(2 * _alpha)
339+
# log_pdf = _lambda * np.log(_gamma2) + _lambda_minus_half * np.log(absz) + np.log(sp.kv(_lambda_minus_half, _alpha * absz)) + \
340+
# _beta * z - _lambda_minus_half * np.log(2 * _alpha) # + const
341+
log_pdf = term1 + term2 + term3 + term4 + term5
266342
return -np.sum(log_pdf)
267343

268344
def log_likelihood_diag(self, w, model, omega, omega_fixed, data_, nu, ell):
@@ -327,6 +403,38 @@ def log_posterior_normal(self, w, omega, omega_fixed, data, nu=6, ell=3):
327403
return self.log_prior_offdiag(w) + self.log_likelihood_normal(w, omega, omega_fixed, data, nu, ell)
328404

329405

406+
def scale_matrix(model, w, omega, omega_fixed, n):
407+
'''
408+
'''
409+
elements = model(omega_fixed, w)(omega)
410+
ie = 0
411+
if elements.dtype == np.complex128:
412+
L = np.zeros((n, n, omega.shape[0]), dtype = np.complex128)
413+
for i, j in zip(*np.triu_indices(n)):
414+
if i == j:
415+
L[i, j] = elements[:, ie]
416+
ie += 1
417+
else:
418+
L[i, j] = elements[:, ie] + 1j*elements[:, ie+1]
419+
ie += 2
420+
421+
S = np.einsum('jiw,jkw->wik', L.conj(), L)
422+
else:
423+
L = np.zeros((n, n, omega.shape[0]))
424+
for i, j in zip(*np.triu_indices(n)):
425+
L[i, j] = elements[:, ie]
426+
ie += 1
427+
428+
S = np.einsum('jiw,jkw->wik', L, L)
429+
return S
430+
431+
def normalize_parameters(p, guess):
432+
mini, maxi = 1.2*np.abs(guess), -1.2*np.abs(guess)
433+
return (p-mini) / (maxi-mini)
434+
435+
def denormalize_parameters(p, guess):
436+
mini, maxi = 1.2*np.abs(guess), -1.2*np.abs(guess)
437+
return p * (maxi-mini) + mini
330438

331439
# # Methods to perform a bayesian estimation of the transport coefficients
332440

0 commit comments

Comments
 (0)