Skip to content

Commit b3cf360

Browse files
SebastienJolySebastienJolyelafmusa
authored
Modified Levenberg Marquardt LOCO (#62)
* Add Levenberg-Marquardt regularization to the SVD LOCO function. * Correct implementation. * Add documentation * Update documentation.yml update actions/upload-artifact version v2 to v4 --------- Co-authored-by: SebastienJoly <[email protected]> Co-authored-by: elafmusa <[email protected]>
1 parent 425c73e commit b3cf360

File tree

2 files changed

+25
-6
lines changed

2 files changed

+25
-6
lines changed

.github/workflows/documentation.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
run: python -m sphinx -b html doc ./doc_build -d ./doc_build
4747

4848
- name: Upload build artifacts # upload artifacts so reviewers can have a quick look without building documentation from the branch locally
49-
uses: actions/upload-artifact@v2
49+
uses: actions/upload-artifact@v4
5050
if: success() && github.event_name == 'pull_request' # only for pushes in PR
5151
with:
5252
name: site-build

pySC/correction/loco.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,15 @@ def loco_correction_lm(initial_guess0, orm_model, orm_measured, Jn, lengths, inc
8989
return result.x
9090

9191

92-
def loco_correction_ng(initial_guess0, orm_model, orm_measured, J, lengths, including_fit_parameters, s_cut, weights=1):
92+
def loco_correction_ng(initial_guess0, orm_model, orm_measured, J, lengths, including_fit_parameters, s_cut, weights=1, LM_lambda=0):
9393
initial_guess = initial_guess0.copy()
9494
mask = _get_parameters_mask(including_fit_parameters, lengths)
9595
residuals = objective(initial_guess[mask], orm_measured - orm_model, J[mask, :, :], weights)
9696
r = residuals.reshape(np.transpose(orm_model).shape)
9797
t2 = np.zeros([len(initial_guess[mask]), 1])
9898
for i in range(len(initial_guess[mask])):
9999
t2[i] = np.sum(np.dot(np.dot(J[i].T, weights), r.T))
100-
return get_inverse(J[mask, :, :], t2, s_cut, weights)
100+
return get_inverse(J[mask, :, :], t2, s_cut, weights, LM_lambda=LM_lambda)
101101

102102

103103
def objective(masked_params, orm_residuals, masked_jacobian, weights):
@@ -157,10 +157,29 @@ def select_equally_spaced_elements(total_elements, num_elements):
157157
return total_elements[::step]
158158

159159

160-
def get_inverse(jacobian, B, s_cut, weights, plot=False):
160+
def get_inverse(jacobian, B, s_cut, weights, LM_lambda=0, plot=False):
161+
"""
162+
Calculates $(J^t J + \lambda diag(J^t J))^{-1} J^t r_0$ using a modified
163+
version of the Levenberg-Marquardt method. More information at p.114 of
164+
https://inis.iaea.org/collection/NCLCollectionStore/_Public/54/003/54003559.pdf
165+
166+
Args:
167+
jacobian: jacobian matrix obtained from calculate_jacobian()
168+
bpm_ords: array of element indices of registered BPMs for which to calculate readings
169+
(for convenience, otherwise `SC.ORD.BPM` is used)
170+
s_cut: number of kept singular values when inverting the matrix
171+
weights: weights applied to the elements of the jacobian
172+
LM_lambda: Levenberg-Marquardt lambda parameter. If LM_lambda=0, the
173+
function is equivalent to the Gauss-Newton method
174+
plot: boolean flag to plot the resulting inverse matrix
175+
176+
Returns:
177+
Array of correction values of the same size as B.
178+
"""
161179
n_resp_mats = len(jacobian)
162-
sum_corr = np.sum(jacobian, axis=2) # Sum over i and j for all planes
163-
matrix = np.dot(np.dot(sum_corr, weights), sum_corr.T)
180+
Jt = np.sum(jacobian, axis=2) # Sum over i and j for all planes
181+
Jt_dot_J = np.dot(np.dot(Jt, weights), Jt.T)
182+
matrix = Jt_dot_J + LM_lambda * np.diag(np.diag(Jt_dot_J))
164183
inv_matrix = sc_tools.pinv(matrix, num_removed=n_resp_mats - min(n_resp_mats, s_cut), plot=plot)
165184
results = np.ravel(np.dot(inv_matrix, B))
166185
# e = np.ravel(np.dot(matrix, results)) - np.ravel(B)

0 commit comments

Comments
 (0)