Source code for cil.optimisation.algorithms.LSQR

#  Copyright 2019 United Kingdom Research and Innovation
#  Copyright 2019 The University of Manchester
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#
# Authors:
# CIL Developers and contributers, listed at: https://github.com/TomographicImaging/CIL/blob/master/NOTICE.txt


from cil.optimisation.algorithms import Algorithm
import numpy
import logging
import warnings 
import math

log = logging.getLogger(__name__)

[docs] class LSQR(Algorithm): r""" Least Squares with QR factorisation (LSQR) algorithm. The LSQR algorithm is used to solve large-scale linear systems and least-squares problems, particularly when the matrix is sparse or implicitly defined. Solves the problem: .. math:: \min_x \|Ax - b\|_2^2 Optionally, with Tikhonov regularisation: .. math:: \min_x \|Ax - b\|_2^2 + \alpha^2 \|x\|_2^2 Parameters ---------- operator : Operator Linear operator representing the forward model. initial : DataContainer, optional Initial guess for the solution. If not provided, a zero-initialised container is used. data : DataContainer Measured data (right-hand side of the equation). alpha : float, optional Non-negative regularisation parameter. If zero, standard LSQR is used. Reference --------- https://web.stanford.edu/group/SOL/software/lsqr/ """ def __init__(self, initial=None, operator=None, data=None, alpha=0, **kwargs): """ Initialise the LSQR algorithm. Parameters ---------- initial : DataContainer, optional Initial guess for the solution. operator : Operator Linear operator representing the forward model. data : DataContainer Measured data. alpha : float, optional Regularisation parameter. Default is 0 (no regularisation). """ super(LSQR, self).__init__(**kwargs) if initial is None and operator is not None: initial = operator.domain_geometry().allocate(0) self.regalpha = alpha if initial is not None and operator is not None and data is not None: self.set_up(initial=initial, operator=operator, data=data) else: raise ValueError(' You must initialise LSQR with an `operator` and `data`')
[docs] def set_up(self, initial, operator, data): """ Set up the LSQR algorithm with the problem definition. Parameters ---------- initial : DataContainer Initial guess for the solution. operator : Operator Linear operator representing the forward model. data : DataContainer Measured data. """ log.info("%s setting up", self.__class__.__name__) self.x = initial.copy() #1 domain self.operator = operator # Initialise Golub-Kahan bidiagonalisation (GKB) #self.u = data - self.operator.direct(self.x) self.u = self.operator.direct(self.x) #1 range self.u.sapyb(-1, data, 1, out=self.u) self.beta = self.u.norm() self.u /= self.beta self.v = self.operator.adjoint(self.u) #2 domain self.alpha = self.v.norm() self.v /= self.alpha self.rhobar = self.alpha self.phibar = self.beta self.normr = self.beta self.regalphasq = self.regalpha**2 self.d = self.v.copy() #3 domain self.tmp_range = data.geometry.allocate(None) #2 range self.tmp_domain = self.x.geometry.allocate(None) #4 domain self.res2 = 0 self.configured = True log.info("%s configured", self.__class__.__name__)
[docs] def update(self): """Perform a single iteration of the LSQR algorithm.""" # Update u in GKB self.operator.direct(self.v, out=self.tmp_range) self.tmp_range.sapyb(1., self.u,-self.alpha, out=self.u) self.beta = self.u.norm() self.u /= self.beta # Update v in GKB self.operator.adjoint(self.u, out=self.tmp_domain) self.v.sapyb(-self.beta, self.tmp_domain, 1., out=self.v) self.alpha = self.v.norm() self.v /= self.alpha # Eliminate diagonal from regularisation if self.regalphasq > 0: rhobar1 = math.sqrt(self.rhobar * self.rhobar + self.regalphasq) c1 = self.rhobar / rhobar1 s1 = self.regalpha / rhobar1 psi = s1 * self.phibar self.phibar = c1 * self.phibar else: rhobar1 = self.rhobar psi = 0 # Eliminate lower bidiagonal part rho = math.sqrt(rhobar1 ** 2 + self.beta ** 2) c = rhobar1 / rho s = self.beta / rho theta = s * self.alpha self.rhobar = -c * self.alpha phi = c * self.phibar self.phibar = s * self.phibar # Update image x self.x.sapyb(1, self.d, phi/rho, out=self.x) # Update d self.d.sapyb(-theta/rho, self.v, 1, out=self.d) # Estimate residual norm self.res2 += psi ** 2 self.normr = math.sqrt(self.phibar ** 2 + self.res2)
[docs] def update_objective(self): """ Update the objective function value (residual norm squared). """ if self.normr is numpy.nan: raise StopIteration() self.loss.append(self.normr**2)