Skip to content

Commit fd580b2

Browse files
authored
Merge pull request #104 from ajeldorado/algodiff_aj
Add regularization to AD-EFC. Add dead actuators.
2 parents d9d384f + a55ff33 commit fd580b2

11 files changed

Lines changed: 118 additions & 49 deletions

File tree

examples/try_running_falco/EXAMPLE_try_running_FALCO_AD_EFC.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,9 @@
5252
mp.ctrl.ad.iprint = 10
5353
mp.ctrl.ad.maxfun = 1000000
5454

55-
56-
mp.ctrl.log10regVec = np.array([-6, ])
57-
58-
55+
mp.ctrl.log10regVec = [0, ]
56+
mp.ctrl.ad.maxiter = 30 # 50
57+
mp.ctrl.ad.iprint = 5
5958

6059
# %% Perform the Wavefront Sensing and Control
6160

falco/ctrl.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -954,7 +954,10 @@ def _ad_efc(ni, vals_list, mp, cvar):
954954
bounds[cvar.uLegend == 2, 0] = dm2lb[mp.dm2.act_ele]
955955
bounds[cvar.uLegend == 2, 1] = dm2ub[mp.dm2.act_ele]
956956

957-
EFendPrev = []
957+
EFend_list = []
958+
Edm1post_list = []
959+
Edm2pre_list = []
960+
DM2surf_list = []
958961
for iMode in range(mp.jac.Nmode):
959962

960963
modvar = falco.config.ModelVariables()
@@ -964,19 +967,23 @@ def _ad_efc(ni, vals_list, mp, cvar):
964967
modvar.starIndex = mp.jac.star_inds[iMode]
965968

966969
# Calculate E-Field for previous EFC iteration
967-
EFend = falco.model.compact(mp, modvar, isNorm=True, isEvalMode=False,
968-
useFPM=True, forRevGradModel=False)
969-
EFendPrev.append(EFend)
970+
EFend, Edm1post, Edm2pre, DM1surf, DM2surf = falco.model.compact(
971+
mp, modvar, isNorm=True, isEvalMode=False, useFPM=True, forRevGradModel=True)
972+
EFend_list.append(EFend)
973+
Edm1post_list.append(Edm1post)
974+
Edm2pre_list.append(Edm2pre)
975+
DM2surf_list.append(DM2surf)
970976

971977
t0 = time.time()
972978

973979
u_sol = scipy.optimize.minimize(
974-
falco.model.compact_reverse_gradient, dm0, args=(mp, cvar.Eest, EFendPrev, log10reg),
980+
falco.model.compact_reverse_gradient, dm0, args=(
981+
log10reg, mp, cvar.Eest, EFend_list, Edm1post_list, Edm2pre_list, DM2surf_list),
975982
method='L-BFGS-B', jac=True, bounds=bounds,
976983
tol=None, callback=None,
977984
options={'disp': None,
978985
'ftol': 1e-99,
979-
'gtol': 1e-10,
986+
'gtol': 1e-99,
980987
'maxls': 20,
981988
'maxiter': mp.ctrl.ad.maxiter,
982989
'maxfun': mp.ctrl.ad.maxfun,
@@ -987,7 +994,6 @@ def _ad_efc(ni, vals_list, mp, cvar):
987994
duVec = u_sol.x
988995
print(u_sol.success)
989996
print(u_sol.nit)
990-
991997
'''
992998
def optim_cost_func(Vdm):
993999
#cost function for L-BFGS

falco/dm.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def gen_surf_from_act(dm, dx, Nout):
7777
dm = discretize_surf(dm, dm.HminStepMethod)
7878
heightMap = dm.VtoH*dm.Vquantized
7979
else: # Quantization not desired; send raw, continuous voltages
80-
heightMap = dm.VtoH*dm.V
80+
heightMap = dm.VtoH * dm.V
8181

8282
if hasattr(dm, 'orientation'):
8383
if dm.orientation.lower() == 'rot0':
@@ -904,6 +904,15 @@ def enforce_constraints(dm):
904904
# 1) Find actuators that exceed min and max values. Any actuators reaching
905905
# those limits are added to the pinned actuator list.
906906

907+
# Create dead actuator map if it doesn't exist already
908+
if not hasattr(dm, 'dead_map'):
909+
dm.dead_map = np.zeros((dm.Nact, dm.Nact), dtype=bool) # initialize
910+
for ii in dm.dead:
911+
dm.dead_map.ravel()[ii] = True
912+
913+
# Apply dead actuator map
914+
dm.V = dm.V * ~dm.dead_map
915+
907916
# Min voltage limit
908917
Vtotal = dm.V + dm.biasMap
909918
new_inds = np.nonzero(Vtotal.flatten() < dm.Vmin)[0] # linear indices of new actuators breaking their bounds

falco/model/jacobians.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,18 @@ def lyot(mp, imode, idm, iact):
229229
specified Zernike mode, DM number, subband, and star.
230230
231231
"""
232+
# Return zeros immediately if it's a dead actuator
233+
if idm == 1:
234+
if iact in mp.dm1.dead:
235+
EFend = np.zeros_like(mp.Fend.corr.maskBool, dtype=complex)
236+
Gchunk = EFend[mp.Fend.corr.maskBool]
237+
return Gchunk
238+
if idm == 2:
239+
if iact in mp.dm2.dead:
240+
EFend = np.zeros_like(mp.Fend.corr.maskBool, dtype=complex)
241+
Gchunk = EFend[mp.Fend.corr.maskBool]
242+
return Gchunk
243+
232244
modvar = falco.config.ModelVariables()
233245
modvar.sbpIndex = mp.jac.sbp_inds[imode]
234246
modvar.zernIndex = mp.jac.zern_inds[imode]
@@ -657,6 +669,18 @@ def vortex(mp, imode, idm, iact):
657669
specified Zernike mode, DM number, subband, and star.
658670
659671
"""
672+
# Return zeros immediately if it's a dead actuator
673+
if idm == 1:
674+
if iact in mp.dm1.dead:
675+
EFend = np.zeros_like(mp.Fend.corr.maskBool, dtype=complex)
676+
Gchunk = EFend[mp.Fend.corr.maskBool]
677+
return Gchunk
678+
if idm == 2:
679+
if iact in mp.dm2.dead:
680+
EFend = np.zeros_like(mp.Fend.corr.maskBool, dtype=complex)
681+
Gchunk = EFend[mp.Fend.corr.maskBool]
682+
return Gchunk
683+
660684
modvar = falco.config.ModelVariables()
661685
modvar.sbpIndex = mp.jac.sbp_inds[imode]
662686
modvar.zernIndex = mp.jac.zern_inds[imode]

falco/model/models.py

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ def full_Fourier(mp, wvl, Ein, normFac, flagScaleFPM=False):
422422
return Eout
423423

424424

425-
def compact_reverse_gradient(command_vec, mp, EestAll, EFendPrev, log10reg):
425+
def compact_reverse_gradient(command_vec, log10reg, mp, EestAll, EFend_list, Edm1post_list, Edm2pre_list, DM2surf_list):
426426
"""
427427
Simplified (aka compact) model used by estimator and controller.
428428
@@ -488,11 +488,10 @@ def compact_reverse_gradient(command_vec, mp, EestAll, EFendPrev, log10reg):
488488
dDM2Vvec[mp.dm2.act_ele] = command_vec[mp.ctrl.uLegend == 2]
489489
dv_dm1 = dDM1Vvec.reshape((mp.dm1.Nact, mp.dm1.Nact))
490490
dv_dm2 = dDM2Vvec.reshape((mp.dm2.Nact, mp.dm2.Nact))
491-
# mp.dm1.V += command_vec[0:mp.dm1.NactTotal].reshape([mp.dm1.Nact, mp.dm1.Nact])
492-
# mp.dm2.V += command_vec[mp.dm2.NactTotal::].reshape([mp.dm2.Nact, mp.dm2.Nact])
493491

494-
total_cost = 0 # initialize
495-
normFacADweightedSum = 0
492+
# initialize
493+
total_cost = 0
494+
# normFacADweightedSum = 0
496495

497496
for imode in range(mp.jac.Nmode):
498497
modvar = falco.config.ModelVariables()
@@ -505,17 +504,18 @@ def compact_reverse_gradient(command_vec, mp, EestAll, EFendPrev, log10reg):
505504
kk = mirrorFac*2*np.pi/wvl
506505
I00 = mp.Fend.compact.I00[modvar.sbpIndex]
507506
# normFac = mp.Fend.compact.I00[modvar.sbpIndex]
507+
# print(f'normFacAD = {normFacAD}')
508508
# normFacFull = np.mean(mp.Fend.full.I00[modvar.sbpIndex, :])
509509
EestVec = EestAll[:, imode]
510510
Eest2D = np.zeros_like(mp.Fend.corr.maskBool, dtype=complex)
511-
Eest2D[mp.Fend.corr.maskBool] = EestVec # * np.sqrt(normFacFull) # Remove normalization
512-
normFacAD = np.sum(np.abs(EestVec)**2)
513-
# print(f'normFacAD = {normFacAD}')
511+
Eest2D[mp.Fend.corr.maskBool] = EestVec
512+
# normFacAD = np.sum(np.abs(EestVec)**2)
514513

515-
# Get model-based E-field before deltas
516-
EFendA, Edm1post, Edm2pre, DM1surf, DM2surf = compact(
517-
mp, modvar, isNorm=True, isEvalMode=isEvalMode, useFPM=useFPM,
518-
forRevGradModel=True)
514+
# Get model-based E-field before deltas. Should be pre-computed for speed.
515+
EFendA = EFend_list[imode]
516+
Edm1post = Edm1post_list[imode]
517+
Edm2pre = Edm2pre_list[imode]
518+
DM2surf = DM2surf_list[imode]
519519

520520
# Get model-based E-field With delta DM commands applied.
521521
mp.dm1.V = mp.dm1.V0 + dv_dm1
@@ -528,17 +528,14 @@ def compact_reverse_gradient(command_vec, mp, EestAll, EFendPrev, log10reg):
528528
mp.dm2.V = mp.dm2.V0.copy()
529529

530530
# Compute the delta E-field from the latest commands (model new - model old).
531-
# EFendA = EFendPrev[imode]
532531
dEend = EFendB - EFendA
533532

534533
# DH = EFend[mp.Fend.corr.maskBool]
535-
# Eest2D = EFendA # DEBUGGING
536534
EdhNew = Eest2D + dEend
537535
DH = EdhNew[mp.Fend.corr.maskBool]
538536
int_in_dh = np.sum(np.abs(DH)**2)
539-
# print(f'int_in_dh = {int_in_dh}')
540537
total_cost += mp.jac.weights[imode] * int_in_dh
541-
normFacADweightedSum += mp.jac.weights[imode] / normFacAD
538+
# normFacADweightedSum += mp.jac.weights[imode] / normFacAD
542539

543540
# Gradient
544541
Fend_masked = mp.jac.weights[imode]*2/np.sqrt(I00)*EdhNew*mp.Fend.corr.maskBool
@@ -612,14 +609,14 @@ def compact_reverse_gradient(command_vec, mp, EestAll, EFendPrev, log10reg):
612609
Edm1_grad = falco.prop.ptp(Edm2_grad, mp.P2.compact.dx*Edm2_grad.shape[0], wvl, -mp.d_dm1_dm2)
613610
surf_dm1_bar = -kk*np.imag(Edm1_grad * np.conj(Edm1post))
614611

615-
surf_dm2_bar_total += mp.jac.weights[imode] * surf_dm2_bar
616-
surf_dm1_bar_total += mp.jac.weights[imode] * surf_dm1_bar
612+
surf_dm2_bar_total += surf_dm2_bar
613+
surf_dm1_bar_total += surf_dm1_bar
617614

618-
# # Calculate DM penalty term component of cost function
619-
# utu_coefs = normFacADweightedSum * mp.ctrl.ad.utu_scale_fac * 10.0**(log10reg)
620-
# total_cost += utu_coefs * np.sum(command_vec**2)
621-
# # print('normFacADweightedSum = %.4g' % normFacADweightedSum)
622-
# # print('utu_coefs = %.4g' % utu_coefs)
615+
# Calculate DM penalty term component of cost function
616+
utu_coef = mp.ctrl.ad.utu_scale_fac * 10.0**(log10reg)
617+
total_cost += utu_coef * np.sum(command_vec**2)
618+
# print('normFacADweightedSum = %.4g' % normFacADweightedSum)
619+
# print('utu_coef = %.4g' % utu_coef)
623620

624621
if mp.dm1.useDifferentiableModel and mp.dm2.useDifferentiableModel:
625622
Vout1 = mp.dm1.differentiableModel.render_backprop(
@@ -630,11 +627,12 @@ def compact_reverse_gradient(command_vec, mp, EestAll, EFendPrev, log10reg):
630627
else:
631628
raise ValueError('mp.dm1.useDifferentiableModel and mp.dm2.useDifferentiableModel must be True for AD-EFC.')
632629

633-
Vout1 *= mp.dm1.VtoH
634-
Vout2 *= mp.dm2.VtoH
630+
Vout1 *= mp.dm1.VtoH * np.conj(~mp.dm1.dead_map)
631+
Vout2 *= mp.dm2.VtoH * np.conj(~mp.dm2.dead_map)
635632
gradient = np.concatenate((Vout1.reshape([mp.dm1.NactTotal])[mp.dm1.act_ele],
636633
Vout2.reshape([mp.dm2.NactTotal])[mp.dm2.act_ele]),
637634
axis=None)
635+
gradient += 2 * utu_coef * gradient
638636

639637
return total_cost, gradient
640638

falco/setup.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,8 @@ def set_optional_variables(mp):
312312
mp.dm1.orientation = 'rot0' # Change to mp.dm1.V orientation before generating DM surface. Options: rot0, rot90, rot180, rot270, flipxrot0, flipxrot90, flipxrot180, flipxrot270
313313
if not hasattr(mp.dm1, 'fitType'):
314314
mp.dm1.fitType = 'linear' # Type of response for displacement vs voltage. Options are 'linear', 'quadratic', and 'fourier2'.
315+
if not hasattr(mp.dm1, 'dead'):
316+
mp.dm1.dead = [] # Linear indices of dead actuators
315317
if not hasattr(mp.dm1, 'pinned'):
316318
mp.dm1.pinned = np.array([]) # Indices of pinned actuators
317319
if not hasattr(mp.dm1, 'Vpinned'):
@@ -346,6 +348,8 @@ def set_optional_variables(mp):
346348
mp.dm2.orientation = 'rot0' # Change to mp.dm2.V orientation before generating DM surface. Options: rot0, rot90, rot180, rot270, flipxrot0, flipxrot90, flipxrot180, flipxrot270
347349
if not hasattr(mp.dm2, 'fitType'):
348350
mp.dm2.fitType = 'linear' # Type of response for displacement vs voltage. Options are 'linear', 'quadratic', and 'fourier2'.
351+
if not hasattr(mp.dm2, 'dead'):
352+
mp.dm2.dead = [] # Linear indices of dead actuators
349353
if not hasattr(mp.dm2, 'pinned'):
350354
mp.dm2.pinned = np.array([]) # Indices of pinned actuators
351355
if not hasattr(mp.dm2, 'Vpinned'):
@@ -1260,7 +1264,14 @@ def falco_configure_dm1_and_dm2(mp):
12601264
mp.dm1.V = np.zeros((mp.dm1.Nact, mp.dm1.Nact))
12611265
if not hasattr(mp.dm2, 'V'):
12621266
mp.dm2.V = np.zeros((mp.dm2.Nact, mp.dm2.Nact))
1263-
pass
1267+
1268+
# Dead actuator 2-D maps
1269+
mp.dm1.dead_map = np.zeros((mp.dm1.Nact, mp.dm1.Nact), dtype=bool)
1270+
mp.dm2.dead_map = np.zeros((mp.dm2.Nact, mp.dm2.Nact), dtype=bool)
1271+
for ii in mp.dm1.dead:
1272+
mp.dm1.dead_map.ravel()[ii] = True
1273+
for ii in mp.dm2.dead:
1274+
mp.dm2.dead_map.ravel()[ii] = True
12641275

12651276
# Initialize the number of elements used per DM
12661277
if np.any(mp.dm_ind == 1):
@@ -1296,7 +1307,7 @@ def falco_gen_dm_stops(mp):
12961307
if mp.flagDM2stop:
12971308
mp.dm2.full.mask = falco.mask.falco_gen_DM_stop(mp.P2.full.dx, mp.dm2.Dstop, mp.centering)
12981309
mp.dm2.compact.mask = falco.mask.falco_gen_DM_stop(mp.P2.compact.dx, mp.dm2.Dstop, mp.centering)
1299-
1310+
13001311
return None
13011312

13021313

tests/functional/test_ad_efc_gradient_lc.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,11 @@ def test_adjoint_model_lc():
131131
# Compute gradient
132132
EestAll = ev.Eest
133133
log10reg = -np.inf
134-
EFendPrev = []
134+
mp.ctrl.ad.utu_scale_fac = 1 # not used when log10reg = -np.inf
135+
EFend_list = []
136+
Edm1post_list = []
137+
Edm2pre_list = []
138+
DM2surf_list = []
135139

136140
falco.ctrl.init(mp, cvar)
137141

@@ -144,10 +148,15 @@ def test_adjoint_model_lc():
144148
modvar.starIndex = mp.jac.star_inds[iMode]
145149

146150
# Calculate E-Field for previous EFC iteration
147-
EFend = falco.model.compact(mp, modvar, isNorm=True, isEvalMode=False,
148-
useFPM=True, forRevGradModel=False)
149-
EFendPrev.append(EFend)
150-
total_cost, u_bar_out = falco.model.compact_reverse_gradient(du, mp, EestAll, EFendPrev, log10reg)
151+
EFend, Edm1post, Edm2pre, DM1surf, DM2surf = falco.model.compact(
152+
mp, modvar, isNorm=True, isEvalMode=False, useFPM=True, forRevGradModel=True)
153+
EFend_list.append(EFend)
154+
Edm1post_list.append(Edm1post)
155+
Edm2pre_list.append(Edm2pre)
156+
DM2surf_list.append(DM2surf)
157+
158+
total_cost, u_bar_out = falco.model.compact_reverse_gradient(
159+
du, log10reg, mp, EestAll, EFend_list, Edm1post_list, Edm2pre_list, DM2surf_list)
151160

152161
u1_bar_out = u_bar_out[0:mp.dm1.NactTotal]
153162
u2_bar_out = u_bar_out[mp.dm1.NactTotal::]

tests/functional/test_ad_efc_gradient_vc.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,11 @@ def test_adjoint_model_vc():
187187
# Compute gradient
188188
EestAll = ev.Eest
189189
log10reg = -np.inf
190-
EFendPrev = []
190+
mp.ctrl.ad.utu_scale_fac = 1 # not used when log10reg = -np.inf
191+
EFend_list = []
192+
Edm1post_list = []
193+
Edm2pre_list = []
194+
DM2surf_list = []
191195

192196
falco.ctrl.init(mp, cvar)
193197

@@ -200,10 +204,15 @@ def test_adjoint_model_vc():
200204
modvar.starIndex = mp.jac.star_inds[iMode]
201205

202206
# Calculate E-Field for previous EFC iteration
203-
EFend = falco.model.compact(mp, modvar, isNorm=True, isEvalMode=False,
204-
useFPM=True, forRevGradModel=False)
205-
EFendPrev.append(EFend)
206-
total_cost, u_bar_out = falco.model.compact_reverse_gradient(du, mp, EestAll, EFendPrev, log10reg)
207+
EFend, Edm1post, Edm2pre, DM1surf, DM2surf = falco.model.compact(
208+
mp, modvar, isNorm=True, isEvalMode=False, useFPM=True, forRevGradModel=True)
209+
EFend_list.append(EFend)
210+
Edm1post_list.append(Edm1post)
211+
Edm2pre_list.append(Edm2pre)
212+
DM2surf_list.append(DM2surf)
213+
214+
total_cost, u_bar_out = falco.model.compact_reverse_gradient(
215+
du, log10reg, mp, EestAll, EFend_list, Edm1post_list, Edm2pre_list, DM2surf_list)
207216

208217
u1_bar_out = u_bar_out[0:mp.dm1.NactTotal]
209218
u2_bar_out = u_bar_out[mp.dm1.NactTotal::]

tests/functional/test_diff_dm_model.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def test_diff_dm_model():
4141
mp.dm1.edgeBuffer = 1 # max radius (in actuator spacings) outside of beam on DM surface to compute influence functions for. [actuator widths]
4242

4343
mp.dm1.fitType = 'linear'
44+
mp.dm1.dead = []
4445
mp.dm1.pinned = np.array([])
4546
mp.dm1.Vpinned = np.zeros_like(mp.dm1.pinned)
4647
mp.dm1.tied = np.zeros((0, 2))

tests/functional/test_dm_orientation.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def test_surface_orientation():
3232
mp.dm1.edgeBuffer = 1 # max radius (in actuator spacings) outside of beam on DM surface to compute influence functions for. [actuator widths]
3333

3434
mp.dm1.fitType = 'linear'
35+
mp.dm1.dead = []
3536
mp.dm1.pinned = np.array([])
3637
mp.dm1.Vpinned = np.zeros_like(mp.dm1.pinned)
3738
mp.dm1.tied = np.zeros((0, 2))
@@ -126,6 +127,7 @@ def test_surface_orientation_from_cube():
126127
mp.dm1.edgeBuffer = 1 # max radius (in actuator spacings) outside of beam on DM surface to compute influence functions for. [actuator widths]
127128

128129
mp.dm1.fitType = 'linear'
130+
mp.dm1.dead = []
129131
mp.dm1.pinned = np.array([])
130132
mp.dm1.Vpinned = np.zeros_like(mp.dm1.pinned)
131133
mp.dm1.tied = np.zeros((0, 2))

0 commit comments

Comments
 (0)