def showFit(self, ax=None, **kwargs): """Show the last inversion data and response.""" ax = self.showData(data=self.inv.dataVals, error=self.inv.errorVals, label='Data', ax=ax, **kwargs) ax = self.showData(data=self.inv.response, label='Response', ax=ax, **kwargs) if not kwargs.pop('hideFittingAnnotation', False): ax.text(0.99, 0.005, r"rrms: {0}, $\chi^2$: {1}".format( pf(self.fw.inv.relrms()), pf(self.fw.inv.chi2())), transform=ax.transAxes, horizontalalignment='right', verticalalignment='bottom', fontsize=8) if not kwargs.pop('hideLegend', False): ax.legend() return ax
def showProgress(self, style='all'): r"""Called if showProgress=True is set for the inversion run. TODO *Discuss .. its a useful function but breaks a little the FrameWork work only concept. """ if self.axs is None: axs = None if style == 'all' or style == True: fig, axs = pg.plt.subplots(1, 2) elif style == 'Model': fig, axs = pg.plt.subplots(1, 1) self.axs = axs ax = self.axs if style == 'Model': for other_ax in ax.figure.axes: # pg._y(type(other_ax).mro()) if type(other_ax).mro()[0] == type(ax): # only clear Axes not Colorbars other_ax.clear() self.fop.drawModel(ax, self.inv.model()) else: # for other_ax in ax[0].figure.axes: # other_ax.clear() for _ax in self.axs: _ax.clear() try: pg.viewer.mpl.twin(_ax).clear() except: pass self.fop.drawModel(ax[0], self.inv.model(), label='Model') self.fop.drawData(ax[1], self._dataVals, self._errorVals, label='Data') self.fop.drawData(ax[1], self.inv.response(), label='Response') ax[1].text(0.99, 0.005, "Iter: {0}, rrms: {1}%, $\chi^2$: {2}".format( self.inv.iter(), pf(self.inv.relrms()), pf(self.inv.chi2())), transform=ax[1].transAxes, horizontalalignment='right', verticalalignment='bottom', fontsize=8) ax[1].figure.tight_layout() pg.plt.pause(0.05)
def run(self, dataVals, errorVals, **kwargs): """Run inversion. The inversion will always start from the starting model taken from the forward operator. If you want to run the inversion from a specified prior model, e.g., from a other run, set this model as starting model to the FOP (fop.setStartModel). Any self.inv.setModel() settings will be overwritten. Parameters ---------- dataVals : iterable Data values errorVals : iterable Relative error values. dv / v Keyword Arguments ----------------- maxIter : int Overwrite class settings for maximal iterations number. dPhi : float [1] Overwrite class settings for delta data phi aborting criteria. Default is 1% """ self.reset() if self.isFrameWork: pg.critical('in use?') return self._inv.run(dataVals, errorVals, **kwargs) if self.fop is None: raise Exception( "Need a valid forward operator for the inversion run.") maxIter = kwargs.pop('maxIter', self.maxIter) minDPhi = kwargs.pop('dPhi', self.minDPhi) self.verbose = kwargs.pop('verbose', self.verbose) self.debug = kwargs.pop('debug', self.debug) self.robustData = kwargs.pop('robustData', False) lam = kwargs.pop('lam', 20) progress = kwargs.pop('progress', None) showProgress = kwargs.pop('showProgress', False) self.inv.setTransModel(self.fop.modelTrans) self.dataVals = dataVals self.errorVals = errorVals sm = kwargs.pop('startModel', None) if sm is not None: self.startModel = sm self.inv.setData(self._dataVals) self.inv.setRelativeError(self._errorVals) self.inv.setLambda(lam) # temporary set max iter to one for the initial run call maxIterTmp = self.maxIter self.maxIter = 1 if self.verbose: pg.info('Starting inversion.') print("fop:", self.inv.fop()) if isinstance(self.dataTrans, pg.trans.TransCumulative): print("Data transformation (cumulative):") for i in range(self.dataTrans.size()): print("\t", i, self.dataTrans.at(i)) else: print("Data transformation:", self.dataTrans) if isinstance(self.modelTrans, pg.trans.TransCumulative): print("Model transformation (cumulative):") for i in range(self.modelTrans.size()): if i < 10: print("\t", i, self.modelTrans.at(i)) else: print(".", end='') else: print("Model transformation:", self.modelTrans) print("min/max (data): {0}/{1}".format(pf(min(self._dataVals)), pf(max(self._dataVals)))) print("min/max (error): {0}%/{1}%".format( pf(100 * min(self._errorVals)), pf(100 * max(self._errorVals)))) print("min/max (start model): {0}/{1}".format( pf(min(self.startModel)), pf(max(self.startModel)))) ### To ensure reproduceability of the run() call inv.start() will ### reset self.inv.model() to fop.startModel(). self.fop.setStartModel(self.startModel) self.inv.setReferenceModel(self.startModel) if self.verbose: print("-" * 80) if self._preStep and callable(self._preStep): self._preStep(0, self) self.inv.start() self.maxIter = maxIterTmp if self._postStep and callable(self._postStep): self._postStep(0, self) if showProgress: self.showProgress(showProgress) lastPhi = self.phi() self.chi2History = [self.chi2()] self.modelHistory = [self.startModel] for i in range(1, maxIter): if self._preStep and callable(self._preStep): self._preStep(i, self) if self.verbose: print("-" * 80) print("inv.iter", i + 1, "... ", end='') try: self.inv.oneStep() except RuntimeError as e: print(e) pg.error('One step failed. ' 'Aborting and going back to last model') if np.isnan(self.model).any(): print(model) pg.critical('invalid model') resp = self.inv.response() chi2 = self.inv.chi2() self.chi2History.append(chi2) self.modelHistory.append(self.model) if showProgress: self.showProgress(showProgress) if self._postStep and callable(self._postStep): self._postStep(i, self) ### we need to check the following before oder after chi2 calc?? self.inv.setLambda(self.inv.getLambda() * self.inv.lambdaFactor()) if self.robustData: self.inv.robustWeighting() if self.inv.blockyModel(): self.inv.constrainBlocky() phi = self.phi() dPhi = phi / lastPhi if self.verbose: print("chiĀ² = {0} (dPhi = {1}%) lam: {2}".format( round(chi2, 2), round((1 - dPhi) * 100, 2), self.inv.getLambda())) if chi2 <= 1 and self.stopAtChi1: print("\n") if self.verbose: pg.boxprint("Abort criterion reached: chiĀ² <= 1 (%.2f)" % chi2) break if (dPhi > (1.0 - minDPhi / 100.0)) and i > 2: # if dPhi < -minDPhi: if self.verbose: pg.boxprint( "Abort criteria reached: dPhi = {0} (< {1}%)".format( round((1 - dPhi) * 100, 2), minDPhi)) break lastPhi = phi ### will never work as expected until we unpack kwargs .. any idea for # better strategy? # if len(kwargs.keys()) > 0: # print("Warning! unused keyword arguments", kwargs) self.model = self.inv.model() return self.model
def showProgress(self, style='all'): r"""Show the inversion progress after every iteration. Can show models if `drawModel` method exists. The default fallback is plotting the :math:`\chi^2` fit as a function of iterations. Called if `showProgress=True` is set for the inversion run. """ if self.fop.drawModel is None: style = 'convergence' if self.axs is None: axs = None if style == 'all' or style is True: fig, axs = pg.plt.subplots(1, 2) else: fig, axs = pg.plt.subplots(1, 1) self.axs = axs ax = self.axs if style == 'Model': for other_ax in ax.figure.axes: # pg._y(type(other_ax).mro()) if type(other_ax).mro()[0] == type(ax): # only clear Axes not Colorbars other_ax.clear() self.fop.drawModel(ax, self.inv.model()) elif style == 'all': # for other_ax in ax[0].figure.axes: # other_ax.clear() for _ax in self.axs: _ax.clear() try: pg.viewer.mpl.twin(_ax).clear() except Exception: pass self.fop.drawModel(ax[0], self.inv.model(), label='Model') self.fop.drawData(ax[1], self._dataVals, self._errorVals, label='Data') self.fop.drawData(ax[1], self.inv.response(), label='Response') ax[1].text( 0.99, 0.005, r"Iter: {0}, rrms: {1}%, $\chi^2$: {2}".format( self.inv.iter(), pf(self.inv.relrms()), pf(self.inv.chi2())), transform=ax[1].transAxes, horizontalalignment='right', verticalalignment='bottom', fontsize=8) ax[1].figure.tight_layout() elif style == 'convergence': ax.semilogy(self.inv.iter(), self.inv.chi2(), "ro") if self.inv.iter() == 1: ax.set_xlabel("Iteration") ax.set_ylabel("$\chi^2$") ax.autoscale(tight=True) ax.axhline(y=1, ls="--") pg.plt.pause(0.05)