Skip to content

Commit 9af7c3a

Browse files
author
eltrompetero
committed
Pseudo fixed
implemented alternative algorithm by default for the Ising problem and now default is the standard one
1 parent 882f0b7 commit 9af7c3a

File tree

3 files changed

+118
-26
lines changed

3 files changed

+118
-26
lines changed

coniii/solvers.py

Lines changed: 112 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,12 +1100,14 @@ def __init__(self, sample,
11001100
if calc_observables_r is None or get_multipliers_r is None:
11011101
self.get_multipliers_r, self.calc_observables_r = define_pseudo_ising_helper_functions(self.n)
11021102

1103-
def solve(self, *args, **kwargs):
1103+
def solve(self, force_general=False, **kwargs):
11041104
"""Uses a general all-purpose optimization to solve the problem using functions
11051105
defined in self.get_multipliers_r and self.calc_observables_r.
11061106
11071107
Parameters
11081108
----------
1109+
force_general : bool, False
1110+
If True, force use of "general" algorithm.
11091111
initial_guess : ndarray, None
11101112
Initial guess for the parameter values.
11111113
solver_kwargs : dict, {}
@@ -1117,13 +1119,91 @@ def solve(self, *args, **kwargs):
11171119
multipliers
11181120
"""
11191121

1120-
return self._solve_general(*args, **kwargs)
1122+
if type(self.model) is Ising and not force_general:
1123+
return self._solve_ising(**kwargs)
1124+
return self._solve_general(**kwargs)
1125+
1126+
def _solve_ising(self,
1127+
initial_guess=None,
1128+
full_output=False,
1129+
solver_kwargs={}):
1130+
"""Solve for Langrangian parameters according to pseudolikelihood algorithm.
1131+
1132+
Parameters
1133+
----------
1134+
initial_guess : ndarray, None
1135+
Initial guess for the parameter values.
1136+
full_output : bool, False
1137+
If True, return output from scipy.minimize() routine.
1138+
solver_kwargs : dict, {}
1139+
Keyword arguments for scipy.optimize.minimize.
1140+
1141+
Returns
1142+
-------
1143+
ndarray
1144+
Solved multipliers.
1145+
dict (optional)
1146+
Output from scipy.optimize.minimize.
1147+
"""
1148+
1149+
if initial_guess is None:
1150+
initial_guess = np.zeros(self.calc_observables(self.sample[0][None,:]).size)
1151+
1152+
# reformat initial_guess for easy looping later
1153+
initial_guessAsMat = replace_diag(squareform(initial_guess[self.n:]), initial_guess[:self.n])
1154+
for i in range(1,self.n):
1155+
tmp = initial_guessAsMat[i,i]
1156+
initial_guessAsMat[i,i] = initial_guessAsMat[i,0]
1157+
initial_guessAsMat[i,0] = tmp
1158+
# initialize variables that will be used later
1159+
obs = [self.calc_observables_r(r, self.sample) for r in range(self.n)]
1160+
soln = [] # list of output from scipy.optimize.minimize for each spin
1161+
Jmat = np.zeros((self.n, self.n)) # couplings stored in matrix format with fields along diagonal
1162+
1163+
# iterate through each spin and solve for parameters for each one
1164+
for r in range(self.n):
1165+
# to use below...
1166+
multipliersrix = self.get_multipliers_r(r, initial_guess)[1]
1167+
guess = initial_guess.copy()
1168+
1169+
# params only change the terms relevant to the particular spin being considered
1170+
def f(params):
1171+
guess[multipliersrix] = params
1172+
multipliers = self.get_multipliers_r(r, guess)[0]
1173+
E = -obs[r].dot(multipliers)
1174+
loglikelihood = -np.log( 1+np.exp(2*E) ).sum()
1175+
dloglikelihood = ( -(1/(1+np.exp(2*E)) * np.exp(2*E))[:,None] * 2*obs[r] ).sum(0)
1176+
return -loglikelihood, dloglikelihood
1177+
1178+
soln.append(minimize(f, initial_guessAsMat[r], jac=True, **solver_kwargs))
1179+
thisMultipliers = soln[-1]['x']
1180+
Jmat[r,r] = thisMultipliers[0]
1181+
Jmat[r,np.delete(np.arange(self.n),r)] = thisMultipliers[1:]
1182+
1183+
# symmetrize couplings
1184+
Jmat = (Jmat + Jmat.T)/2
1185+
self.multipliers = np.concatenate((Jmat.diagonal(), squareform(zero_diag(Jmat))))
1186+
1187+
if full_output:
1188+
return self.multipliers, soln
1189+
return self.multipliers
11211190

11221191
def _solve_general(self,
11231192
initial_guess=None,
11241193
full_output=False,
11251194
solver_kwargs={}):
1126-
"""Solve for Langrangian parameters according to pseudolikelihood algorithm.
1195+
"""Solve for Langrangian parameters according to a variation on the
1196+
pseudolikelihood algorithm detailed in Aurell and Ekeberg (PRL, 2012). There, the
1197+
conditional log-likelihoods per spin are minimized independently and then the
1198+
resulting couplings are combined in a way that ensures that the interactions are
1199+
symmetric. The generalization is straightforward for higher-order interactions
1200+
(normalize by the order of the interaction), but here present a different approach
1201+
that is somewhat computationally simpler.
1202+
1203+
The *sum* of the conditional likelihoods over each spin is minimized, ensuring
1204+
that the parameters are equal across all conditional likelihood equations by
1205+
construction. In general, this gives different results from the normal
1206+
pseudolikelihood formulation, but they agree closely in many cases.
11271207
11281208
Parameters
11291209
----------
@@ -1132,61 +1212,62 @@ def _solve_general(self,
11321212
full_output : bool, False
11331213
If True, return output from scipy.minimize() routine.
11341214
solver_kwargs : dict, {}
1135-
kwargs for scipy.minimize().
1215+
Keyword arguments for scipy.optimize.minimize.
11361216
11371217
Returns
11381218
-------
11391219
ndarray
1140-
multipliers
1220+
Solved multipliers.
11411221
dict (optional)
1142-
Output from scipy.minimize.
1222+
Output from scipy.optimize.minimize.
11431223
"""
11441224

11451225
if initial_guess is None:
11461226
initial_guess = np.zeros(self.calc_observables(self.sample[0][None,:]).size)
1227+
obs = [self.calc_observables_r(r, self.sample) for r in range(self.n)]
11471228

1148-
def f(params,
1149-
n=self.n,
1150-
calc_observables_r=self.calc_observables_r,
1151-
get_multipliers_r=self.get_multipliers_r):
1229+
def f(params):
1230+
# running sums of function evaluations over all spins
11521231
loglikelihood = 0
11531232
dloglikelihood = np.zeros_like(initial_guess) # gradient
11541233

11551234
# iterate through each spin
1156-
for r in range(n):
1157-
obs = calc_observables_r(r, self.sample)
1158-
multipliers, multipliersrix = get_multipliers_r(r,params)
1159-
E = -obs.dot(multipliers)
1235+
for r in range(self.n):
1236+
multipliers, multipliersrix = self.get_multipliers_r(r, params)
1237+
E = -obs[r].dot(multipliers)
11601238
loglikelihood += -np.log( 1+np.exp(2*E) ).sum()
1161-
dloglikelihood[multipliersrix] += ( -(1/(1+np.exp(2*E)) * np.exp(2*E))[:,None] * 2*obs ).sum(0)
1239+
dloglikelihood[multipliersrix] += ( -(1/(1+np.exp(2*E)) *
1240+
np.exp(2*E))[:,None] *
1241+
2*obs[r] ).sum(0)
11621242
return -loglikelihood, dloglikelihood
11631243

11641244
soln = minimize(f, initial_guess, jac=True, **solver_kwargs)
11651245
self.multipliers = soln['x']
11661246
if full_output:
1167-
return soln['x'],soln
1247+
return soln['x'], soln
11681248
return soln['x']
11691249

1170-
def _solve_ising(self, initial_guess=None, full_output=False):
1171-
"""Solve Ising model specifically with Pseudo.
1250+
def _solve_ising_deprecated(self, initial_guess=None, full_output=False):
1251+
"""Deprecated.
11721252
11731253
Parameters
11741254
----------
11751255
initial_guess : ndarray, None
11761256
Pseudo for Ising doesn't use a starting point. This is syntactic sugar.
1257+
full_output : bool, False
11771258
11781259
Returns
11791260
-------
11801261
ndarray
1181-
multipliers
1262+
Solved multipliers.
11821263
"""
11831264

11841265
X = self.sample
11851266
X = (X + 1)/2 # change from {-1,1} to {0,1}
11861267

11871268
# start at freq. model params?
11881269
freqs = np.mean(X, axis=0)
1189-
hList = -np.log(freqs/(1.-freqs))
1270+
hList = -np.log(freqs / (1. - freqs))
11901271
Jfinal = np.zeros((self.n,self.n))
11911272

11921273
for r in range(self.n):
@@ -1216,6 +1297,8 @@ def _solve_ising(self, initial_guess=None, full_output=False):
12161297

12171298
def cond_log_likelihood(self, r, X, Jr):
12181299
"""Equals the conditional log likelihood -L_r.
1300+
1301+
Deprecated.
12191302
12201303
Parameters
12211304
----------
@@ -1246,6 +1329,8 @@ def cond_log_likelihood(self, r, X, Jr):
12461329

12471330
def cond_jac(self, r, X, Jr):
12481331
"""Returns d cond_log_likelihood / d Jr, with shape (dimension of system)
1332+
1333+
Deprecated.
12491334
"""
12501335

12511336
X,Jr = np.array(X),np.array(Jr)
@@ -1267,6 +1352,8 @@ def cond_hess(self, r, X, Jr, pairCoocRhat=None):
12671352
Current implementation uses more memory for speed. For large sample size, it may
12681353
make sense to break up differently if too much memory is being used.
12691354
1355+
Deprecated.
1356+
12701357
Parameters
12711358
----------
12721359
pairCooc : ndarray, None
@@ -1296,14 +1383,18 @@ def pair_cooc_mat(self, X):
12961383
For use with cond_hess.
12971384
12981385
Slow because I haven't thought of a better way of doing it yet.
1386+
1387+
Deprecated.
12991388
"""
13001389

13011390
p = [ np.outer(f,f) for f in X ]
13021391
return np.transpose(p,(1,0,2))
13031392

1304-
def pseudo_log_likelhood(self, X, J):
1393+
def pseudo_log_likelihood(self, X, J):
13051394
"""TODO: Could probably be made more efficient.
13061395
1396+
Deprecated.
1397+
13071398
Parameters
13081399
----------
13091400
X : ndarray

coniii/utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -766,8 +766,9 @@ def get_multipliers_r(r, multipliers, N=N):
766766

767767
ix = [r]
768768
multipliersr = np.zeros(N)
769-
multipliersr[0] = multipliers[r]
769+
multipliersr[0] = multipliers[r] # first entry is the biasing field
770770

771+
# fill in the couplings
771772
ixcounter = 1
772773
for i in range(N):
773774
if i!=r:

ipynb/usage_guide.ipynb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@
311311
},
312312
{
313313
"cell_type": "code",
314-
"execution_count": 8,
314+
"execution_count": 23,
315315
"metadata": {
316316
"scrolled": true
317317
},
@@ -320,8 +320,8 @@
320320
"name": "stdout",
321321
"output_type": "stream",
322322
"text": [
323-
"Error on sample corr: 1.817264E-04\n",
324-
"Error on multipliers: 1.209661E-01\n"
323+
"Error on sample corr: 1.866256E-04\n",
324+
"Error on multipliers: 1.209458E-01\n"
325325
]
326326
}
327327
],
@@ -333,7 +333,7 @@
333333
},
334334
{
335335
"cell_type": "code",
336-
"execution_count": 9,
336+
"execution_count": 24,
337337
"metadata": {},
338338
"outputs": [
339339
{

0 commit comments

Comments
 (0)