Skip to content

Commit eafbfe0

Browse files
committed
Trimming function discarded in favor of sparsifier
1 parent f90dcdc commit eafbfe0

File tree

3 files changed

+49
-133
lines changed

3 files changed

+49
-133
lines changed

src/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def self_pickle(filename):
8383
# Quadratic optimization step
8484
H1_tolerance = 1e-5
8585
curves_list_length_lim = 1000
86+
curves_list_length_min = 10
8687
CVXOPT_TOL = 1e-25
8788

8889
# Gradient flow + coefficient optimization parameters

src/insertion_step.py

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,39 +25,37 @@ def insertion_step(current_measure):
2525
"""
2626
assert isinstance(current_measure, curves.measure)
2727
insertion_mod.initialize(current_measure)
28-
f_t = config.f_t
2928
logger = config.logger
30-
# compute the dual variable
31-
w_t = op.w_t(current_measure)
32-
# stationary_curves is a list of curves.curve objects, which are inserted in an
33-
# ordered fashion, with the order defined by their respectve F(γ) value,
34-
# pointed out in the energy_curves list.
35-
logger.status([1,1,0])
36-
# We use multistart descent to look for the global minimum. We obtain for free
37-
# a list of stationary curves.
38-
stationary_curves, energy_curves = multistart_descent(current_measure)
29+
# stationary_curves is a list of curves.curve objects, which are inserted
30+
# in an ordered fashion, with the order defined by their respectve F(γ)
31+
# value, pointed out in the energy_curves list.
32+
logger.status([1, 1, 0])
33+
# We use multistart descent to look for the global minimum. We obtain for
34+
# free a list of stationary curves.
35+
stationary_curves, energy_curves = multistart_descent(current_measure)
3936
# log the found stationary curves
40-
logger.status([1,2,0], stationary_curves, energy_curves)
37+
logger.status([1, 2, 0], stationary_curves, energy_curves)
4138
# log the dual gap
4239
dual_gap = opt.dual_gap(current_measure, stationary_curves)
43-
logger.status([1,2,5], dual_gap)
40+
logger.status([1, 2, 5], dual_gap)
4441
# Exit condition
4542
insertion_eps = config.insertion_eps
4643
if dual_gap < insertion_eps:
47-
#if energy_curves[1] >= -1 - insertion_eps:
48-
logger.status([1,2,4])
49-
exit_flag = 0 # the algorithm stops
44+
logger.status([1, 2, 4])
45+
exit_flag = 0 # the algorithm stops
5046
return current_measure, exit_flag
5147
else:
5248
# We proceed with the weight optimization step
49+
max_curve_num = config.curves_list_length_lim
50+
max_stationary = max_curve_num - len(current_measure.curves)
51+
max_stationary = max(max_stationary, config.curves_list_length_min)
5352
candidate_measure = curves.measure()
5453
for curve in current_measure.curves:
5554
candidate_measure.add(curve, 1)
56-
for curve in stationary_curves:
55+
for curve in stationary_curves[:max_stationary]:
5756
candidate_measure.add(curve, 1)
5857
# Optimize the coefficients and create get a measure from them
59-
candidate_measure = opt.weight_optimization_step(candidate_measure,
60-
energy_curves)
58+
candidate_measure = opt.weight_optimization_step(candidate_measure)
6159
exit_flag = 1
6260
return candidate_measure, exit_flag
6361

src/optimization.py

Lines changed: 32 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -142,107 +142,13 @@ def after_optimization_sparsifier(current_measure, energy_curves=None):
142142
id2 = id1 + 1
143143
return output_measure
144144

145-
146-
def measure_trimming(current_measure, energy_curves=None):
147-
""" Trim a sparse measure by decreasing the number of members.
148-
149-
This function takes an input measure and the proceeds to delete replicated
150-
atoms. If this is done in the insertion step, it furthers limits the number
151-
of members if they exceed a preset value (defined in config.py as
152-
curves_list_length_lim).
153-
There are two execution cases:
154-
case 1 - the vector energy_curves is provided, therefore it just checks for
155-
duplicates, deletes it and return.
156-
case 2 - the vector energy_curves is provided. In this case it is implicit
157-
that the trimming is ocurring during the insertion step, therefore the
158-
input measure is the union of both an original measure, with the stationary
159-
points. The stationary points are those that get further trummed if their
160-
energy (given by the energy_curves vector) is too low.
161-
162-
:param current_measure: The target measure to be trimmed.
163-
:type current_measure: class:`src.curves.measure`
164-
:param energy_curves: list of F(γ) values for the curves obtained by the
165-
`multistart_descent` method, defaults to None. :type energy_curves: list,
166-
optional
167-
"""
168-
# ---------------
169-
# Inputs:
170-
# current_measure (curves.measure type):
171-
# the measure to be trimmed.
172-
# Output:
173-
# curves_list (list of curves.curve type object):
174-
# The curves that survive the trimming. These are the atoms of the
175-
# new measure.
176-
# Kwargs:
177-
# energy_curves (list of np.float, default None):
178-
# If available, list of F(γ) values for the member curves of the
179-
# set of stationary points. It accelerates comparisons.
180-
# --------------
181-
# Preset parameters:
182-
# config.H1_tolerance
183-
# config.curves_list_length_lim
184-
curves_list = copy.deepcopy(current_measure.curves)
185-
if energy_curves is None:
186-
duplicates_idx = []
187-
for i, curve1 in enumerate(curves_list):
188-
for curve2 in curves_list[i+1:]:
189-
if (curve1-curve2).H1_norm() < config.H1_tolerance:
190-
duplicates_idx.append(i)
191-
break
192-
# take out the duplicated curves
193-
for i in reversed(duplicates_idx):
194-
curves_list.pop(i)
195-
else:
196-
# Get the number of current curves and tabu curves
197-
N_current_curves = len(curves_list)-len(energy_curves)
198-
# separate curves
199-
current_curves = curves_list[0:N_current_curves]
200-
stationary_curves = curves_list[N_current_curves:]
201-
# Order the tabu curves
202-
sort_idx = np.argsort(energy_curves)
203-
stationary_curves = [stationary_curves[i] for i in sort_idx]
204-
energy_curves_list = [energy_curves[i] for i in sort_idx]
205-
# Eliminate duplicate curves, using the information of the
206-
# energy_curves (if possible) to accelerate this process
207-
duplicates_idx = []
208-
# The current curves should not be duplicated, we check with the
209-
# tabu curves if they are duplicated.
210-
for curve1 in current_curves:
211-
for idx, curve2 in enumerate(stationary_curves):
212-
if (curve1-curve2).H1_norm() < config.H1_tolerance:
213-
duplicates_idx.append(idx)
214-
## eliminate duplicated idx's and sort them
215-
duplicates_idx = list(dict.fromkeys(duplicates_idx))
216-
duplicates_idx.sort(reverse=True)
217-
# remove the duplicate tabu curves
218-
for i in duplicates_idx:
219-
stationary_curves.pop(i)
220-
energy_curves_list.pop(i)
221-
print("Eliminating duplicas, eliminated {} duplicate tabu curves".format(
222-
len(duplicates_idx)))
223-
# Tabu curves should not be replicated given the Tabu search algorith.
224-
# Now trim if the curves_list is too long
225-
pop_counter = 0
226-
while len(stationary_curves) + N_current_curves > config.curves_list_length_lim and \
227-
energy_curves_list[-1] >= -1:
228-
# find the one with least energy and pop it
229-
stationary_curves.pop()
230-
energy_curves_list.pop()
231-
pop_counter += 1
232-
if pop_counter > 0:
233-
print("Trimming process: {} low energy tabu curves eliminated".format(
234-
pop_counter))
235-
curves_list = current_curves + stationary_curves # joining two lists
236-
return curves_list
237-
238-
def solve_quadratic_program(current_measure, energy_curves=None):
145+
def solve_quadratic_program(current_measure):
239146
assert isinstance(current_measure, curves.measure)
240147
# Build the quadratic system of step 5 and then use some generic python
241148
# solver to get a solution.
242149
# Build matrix Q and vector b
243150
logger = config.logger
244151
# First, check that no curves are duplicated
245-
# curves_list = measure_trimming(current_measure, energy_curves=energy_curves)
246152
curves_list = current_measure.curves
247153
N = len(curves_list)
248154
Q = np.zeros((N, N), dtype=float)
@@ -262,20 +168,7 @@ def solve_quadratic_program(current_measure, energy_curves=None):
262168
# Theoretically, Q is positive semi-definite. Numerically, it might not.
263169
# We force Q to be positive semi-definite for the cvxopt solver to work
264170
# this is done simply by replacing the negative eigenvalues with 0
265-
minEigVal = 0
266-
eigval, eigvec = np.linalg.eigh(Q)
267-
if min(eigval) < minEigVal:
268-
# truncate
269-
print("Negative eigenvalues: ",
270-
[eig for eig in eigval if eig < minEigVal])
271-
eigval = np.maximum(eigval, minEigVal)
272-
# Recompute Q = VΣV^(-1)
273-
Q2 = np.linalg.solve(eigvec.T, np.diag(eigval)@eigvec.T).T
274-
print("PSD projection relative norm difference:",
275-
np.linalg.norm(Q-Q2)/np.linalg.norm(Q))
276-
QQ = Q2
277-
else:
278-
QQ = Q
171+
QQ = to_positive_semidefinite(Q)
279172
try:
280173
Qc = cvxopt.matrix(QQ)
281174
bb = cvxopt.matrix(1 - b.reshape(-1, 1))
@@ -291,14 +184,38 @@ def solve_quadratic_program(current_measure, energy_curves=None):
291184
logger.status([1, 2, 2], coefficients)
292185
return curves_list, coefficients
293186

294-
def weight_optimization_step(current_measure, energy_curves=None):
187+
def to_positive_semidefinite(Q):
188+
""" Takes a symmetric matrix and returns a positive semidefinite projection
189+
190+
Parameters
191+
----------
192+
Q : numpy.ndarray
193+
symmetric matrix
194+
195+
Returns
196+
-------
197+
numpy.ndarray, symmetric positive semidefinite matrix.
198+
"""
199+
min_eigval = 0
200+
eigval, eigvec = np.linalg.eigh(Q)
201+
if min(eigval) < min_eigval:
202+
# truncate
203+
print("Negative eigenvalues: ",
204+
[eig for eig in eigval if eig < min_eigval])
205+
eigval = np.maximum(eigval, min_eigval)
206+
# Recompute Q = VΣV^(-1)
207+
Q2 = np.linalg.solve(eigvec.T, np.diag(eigval)@eigvec.T).T
208+
print("PSD projection relative norm difference:",
209+
np.linalg.norm(Q-Q2)/np.linalg.norm(Q))
210+
return Q2
211+
return Q
212+
213+
def weight_optimization_step(current_measure):
295214
config.logger.status([1, 2, 1])
296215
# optimizes the coefficients for the current_measure
297216
# The energy_curves is a vector of energy useful for the trimming process
298217
assert isinstance(current_measure, curves.measure)
299-
curves_list, coefficients =\
300-
solve_quadratic_program(current_measure,
301-
energy_curves = energy_curves)
218+
curves_list, coefficients = solve_quadratic_program(current_measure)
302219
new_current_measure = curves.measure()
303220
for curve, intensity in zip(curves_list, coefficients):
304221
new_current_measure.add(curve, intensity)
@@ -316,7 +233,7 @@ def slide_and_optimize(current_measure):
316233
total_iterations = 0
317234
while total_iterations <= config.g_flow_opt_max_iter:
318235
current_measure, stepsize, iters = gradient_descent(current_measure,
319-
stepsize)
236+
stepsize)
320237
total_iterations += config.g_flow_opt_in_between_iters
321238
current_measure = weight_optimization_step(current_measure)
322239
if stepsize < config.g_flow_limit_stepsize:
@@ -330,7 +247,7 @@ def slide_and_optimize(current_measure):
330247
return current_measure
331248

332249
def gradient_descent(current_measure, init_step,
333-
max_iter = config.g_flow_opt_in_between_iters):
250+
max_iter=config.g_flow_opt_in_between_iters):
334251
# We apply the gradient flow to simultaneously perturb the position of all
335252
# the current curves defining the measure.
336253
# Input: current_measure, measure type object.

0 commit comments

Comments
 (0)