Skip to content

Commit 604ea9b

Browse files
committed
tunable proximal refinement iterations
1 parent 51cd2b2 commit 604ea9b

2 files changed

Lines changed: 34 additions & 19 deletions

File tree

src/aspire/abinitio/commonline_nug.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def __init__(
3838
mult=1.5,
3939
S2_grid=441,
4040
Nstep_yI=10,
41-
perform_pr=False,
41+
pr_iters=None,
4242
verbose=True,
4343
**kwargs,
4444
):
@@ -62,7 +62,8 @@ def __init__(
6262
:param mult: Step-size multiplier for the ADMM primal update.
6363
:param S2_grid: Number of sphere samples used to discretize SO(3).
6464
:param Nstep_yI: Number of inequality-multiplier updates per ADMM iteration.
65-
:param perform_pr: Whether to apply proximal refinement after ADMM.
65+
:param pr_iters: Number of proximal refinement iterations. Default of None
66+
does not perform proximal refinement. Recommended value is 4.
6667
:param verbose: Whether to log ADMM progress.
6768
"""
6869

@@ -85,7 +86,6 @@ def __init__(
8586
self.mult = mult
8687
self.S2_grid = S2_grid
8788
self.Nstep_yI = Nstep_yI
88-
self.perform_pr = perform_pr
8989
self.verbose = verbose
9090

9191
# Handle symmetry
@@ -110,6 +110,13 @@ def __init__(
110110
self.sym_euler = self.sym_grp.rotations.angles
111111
self.n_sym = len(self.sym_euler)
112112

113+
# Set up proximal refinement terms
114+
if pr_iters is not None:
115+
self.pr_weights = 1 / (1 + np.arange(self.Lmax))
116+
self.pr_penalty = [1] * pr_iters
117+
self.pr_rank = list(range(pr_iters - 1, -1, -1)) # [pr_iters - 1,..., 0]
118+
self.pr_iters = pr_iters
119+
113120
self._build_full_pft()
114121

115122
def _build_full_pft(self):
@@ -276,15 +283,12 @@ def perform_admm(self):
276283
"""
277284
X_est = self.admm_sym_J(self.C, self.verbose)
278285

279-
if self.perform_pr:
280-
weight = 1 / (1 + np.arange(self.Lmax))
281-
Penalty = [1, 1, 1, 1]
282-
r = [3, 2, 1, 0]
286+
if self.pr_iters is not None:
283287
X_est = self.proximal_refine(
284288
X_est,
285-
weight,
286-
Penalty,
287-
r,
289+
self.pr_weights,
290+
self.pr_penalty,
291+
self.pr_rank,
288292
)
289293
self.X_est = X_est
290294

@@ -980,11 +984,10 @@ def low_rank_proj(X, r_step):
980984
Xproj.append(tmp)
981985
return Xproj
982986

983-
Niter = len(r)
984987
CC = [None] * self.Lmax
985988
current = [np.copy(Xk) for Xk in X_admm]
986989

987-
for step in range(Niter):
990+
for step in range(self.pr_iters):
988991
X_proj = low_rank_proj(current, r[step])
989992

990993
for k in range(self.Lmax):
@@ -999,7 +1002,7 @@ def low_rank_proj(X, r_step):
9991002
logger.info(
10001003
"Proximal refine step %d/%d: relative update %.3e",
10011004
step + 1,
1002-
Niter,
1005+
self.pr_iters,
10031006
rel_change(X_next, current),
10041007
)
10051008

tests/test_nug.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
N_IMG = [15]
1212
OFFSETS = [0, None]
1313
ORDER = [3, 4]
14-
PR = [False]
14+
PR = [None]
1515
SEED = 1980
1616
VOLUME = [
1717
CnSymmetricVolume,
@@ -89,7 +89,8 @@ def orient_est(source, proximal_refine):
8989
source,
9090
max_shift=max_shift,
9191
shift_step=shift_step,
92-
perform_pr=proximal_refine,
92+
max_iter=201,
93+
pr_iters=proximal_refine,
9394
verbose=False,
9495
)
9596
orient_est.estimate_rotations()
@@ -101,7 +102,7 @@ def orient_est(source, proximal_refine):
101102
#########
102103

103104

104-
def test_smoke_nug(dtype, Volume):
105+
def test_smoke(dtype, Volume):
105106
"""
106107
Perform quick smoke test since other tests are long running.
107108
"""
@@ -122,7 +123,6 @@ def test_smoke_nug(dtype, Volume):
122123
max_iter=10,
123124
S2_grid=50,
124125
max_shift=0,
125-
mask=False,
126126
verbose=False,
127127
)
128128

@@ -151,7 +151,11 @@ def test_dtypes(orient_est):
151151

152152
@pytest.mark.expensive
153153
def test_estimate_rotations_pairwise(orient_est):
154-
""" """
154+
"""
155+
Check mean squared error between estimates and ground truth
156+
pairwise rotations, Rij. This serves as a reference to the error
157+
metric used by the researcher in the related publication.
158+
"""
155159
MSE = compare_rots_sym(
156160
orient_est.rotations, orient_est.src.rotations, orient_est.sym_grp
157161
)
@@ -160,13 +164,21 @@ def test_estimate_rotations_pairwise(orient_est):
160164

161165
@pytest.mark.expensive
162166
def test_estimate_rotations(orient_est):
167+
"""
168+
Check that the mean angular distance between estimates and ground
169+
truth, after symmetry synchronization and global alignment, are
170+
within 10 degrees.
171+
"""
163172
gt_rots_synced = g_sync(
164173
orient_est.rotations, orient_est.src.rotations, orient_est.sym_grp
165174
)
166-
mean_aligned_angular_distance(orient_est.rotations, gt_rots_synced, 8.0)
175+
mean_aligned_angular_distance(orient_est.rotations, gt_rots_synced, 10.0)
167176

168177

169178
def test_unspupported_symmetry_raises(dtype):
179+
"""
180+
Check that we raise for symmetries other than Cn/Dn.
181+
"""
170182
vol = TSymmetricVolume(L=16, C=1, K=10, dtype=dtype).generate()
171183
src = Simulation(n=3, vols=vol)
172184

0 commit comments

Comments
 (0)