def fitCCC(f, amp, phi, eRho=0.01, ePhi=0.001, lam=1000., mstart=None, taupar=(1e-2, 1e-5, 100), cpar=(0.5, 0, 1)): """Fit complex spectrum by Cole-Cole model.""" fCC = ColeColeComplex(f) tLog = pg.RTransLog() fCC.region(0).setStartValue(max(amp)) if mstart is None: # compute from amplitude decay mstart = 1. - min(amp) / max(amp) fCC.region(1).setParameters(mstart, 0, 1) # m (start,lower,upper) fCC.region(2).setParameters(*taupar) # tau fCC.region(3).setParameters(*cpar) # c data = pg.cat(amp, phi) ICC = pg.RInversion(data, fCC, False) # set up inversion class ICC.setTransModel(tLog) error = pg.cat(eRho * amp, pg.RVector(len(f), ePhi)) ICC.setAbsoluteError(error) # perr + ePhi/data) ICC.setLambda(lam) # start with large damping and cool later ICC.setMarquardtScheme(0.8) # lower lambda by 20%/it., no stop chi=1 model = np.asarray(ICC.run()) # run inversion ICC.echoStatus() response = np.asarray(ICC.response()) return model, response[:len(f)], response[len(f):]
def setData(self, data): """TODO.""" if isinstance(data, list): if len(data) == len(self.managers): self.tD.clear() self.dataVals.clear() self.dataErrs.clear() self.fop.setData(data) for i, mgr in enumerate(self.managers): t = mgr.tD self.tD.add(t, data[i].size()) self.dataVals = pg.cat(self.dataVals, data[i](mgr.dataToken())) if mgr.errIsAbsolute: self.dataErrs = pg.cat( self.dataErrs, data[i]('err') / data[i](mgr.dataToken())) else: self.dataErrs = pg.cat(self.dataErrs, data[i]('err')) self.data = data self.inv.setTransData(self.tD) self.inv.setTransModel(self.tM) else: raise BaseException("To few datacontainer given")
def block1dInversionNew(self, nlay=2, lam=100.0, verbose=True): """invert all data together by a 1D model (more general solution)""" data, error = pg.RVector(), pg.RVector() for mrs in self.mrs: data = pg.cat(data, mrs.data) error = pg.cat(error, mrs.error) f = JointMRSModelling(self.mrs, nlay) mrsobj = self.mrs[0] for i in range(3): f.region(i).setParameters(mrsobj.startval[i], mrsobj.lowerBound[i], mrsobj.upperBound[i]) # f.region(0).setStartValue(mrsobj.startval[0]) # f.region(1).setStartValue(mrsobj.startval[1]) # f.region(2).setStartValue(mrsobj.startval[2]) # # Model transformation instances saved in class # transTH = pg.RTransLogLU(mrsobj.lowerBound[0], mrsobj.upperBound[0]) # transWC = pg.RTransLogLU(mrsobj.lowerBound[1], mrsobj.upperBound[1]) # transT2 = pg.RTransLogLU(mrsobj.lowerBound[2], mrsobj.upperBound[2]) # f.region(0).setTransModel(transTH) # f.region(1).setTransModel(transWC) # f.region(2).setTransModel(transT2) INV = pg.RInversion(data, f, verbose) INV.setLambda(lam) INV.setMarquardtScheme(0.8) INV.stopAtChi1(False) # now in MarquardtScheme INV.setDeltaPhiAbortPercent(0.5) INV.setAbsoluteError(error) model = INV.run() return model
def setData(self, data): """ """ if type(data) is list: if len(data) == len(self.managers): self.tD.clear() self.dataVals.clear() self.dataErrs.clear() self.fop.setData(data) for i, mgr in enumerate(self.managers): t = mgr.tD self.tD.add(t, data[i].size()) self.dataVals = pg.cat(self.dataVals, data[i](mgr.dataToken())) if mgr.errIsAbsolute: self.dataErrs = pg.cat(self.dataErrs, data[i]('err')/data[i](mgr.dataToken())) else: self.dataErrs = pg.cat(self.dataErrs, data[i]('err')) self.data = data self.inv.setTransData(self.tD) self.inv.setTransModel(self.tM) else: raise BaseException("To few datacontainer given")
def runEA(self,nlay=None,type='GA',pop_size=100,max_evaluations=10000,**kwargs): import inspyred import random def mygenerate( random, args ): """ generate a random vector of model size """ return [random.random() for i in range( nlay*3 - 1 )] def my_observer(population, num_generations, num_evaluations, args): best = min(population) print('{0:6} -- {1}'.format(num_generations,best.fitness)) @inspyred.ec.evaluators.evaluator def datafit( individual, args ): misfit = (self.data-self.f.response(self.genMod(individual)))/self.error return np.mean(misfit**2) # prepare forward operator if self.f is None or (nlay is not None and nlay is not self.nlay): self.createFOP(nlay) lowerBound = pg.cat( pg.cat( pg.RVector(self.nlay-1,self.lowerBound[0]), pg.RVector(self.nlay,self.lowerBound[1])), pg.RVector(self.nlay,self.lowerBound[2]) ) upperBound = pg.cat( pg.cat( pg.RVector(self.nlay-1,self.upperBound[0]), pg.RVector(self.nlay,self.upperBound[1])), pg.RVector(self.nlay,self.upperBound[2]) ) if self.logpar: self.lLB, self.lUB = pg.log(lowerBound), pg.log(upperBound) # ready mapping functions else: self.lLB, self.lUB = lowerBound, upperBound # self.f = MRS1dBlockQTModelling(nlay, self.K, self.z, self.t) # setup random generator rand = random.Random() rand.seed(int(time.time())) # choose among different evolution algorithms if type == 'GA': ea = inspyred.ec.GA(rand) ea.variator = [inspyred.ec.variators.blend_crossover, inspyred.ec.variators.gaussian_mutation] ea.selector = inspyred.ec.selectors.tournament_selection ea.replacer = inspyred.ec.replacers.generational_replacement if type == 'SA': ea = inspyred.ec.SA(rand) if type == 'DEA': ea = inspyred.ec.DEA(rand) if type == 'PSO': ea = inspyred.swarm.PSO(rand) if type == 'ACS': ea = inspyred.swarm.ACS(rand,[]) if type == 'ES': ea = inspyred.ec.ES(rand) ea.terminator = [inspyred.ec.terminators.evaluation_termination, inspyred.ec.terminators.diversity_termination] else: ea.terminator = inspyred.ec.terminators.evaluation_termination #ea.observer = my_observer ea.observer = [inspyred.ec.observers.stats_observer, inspyred.ec.observers.file_observer] self.pop = ea.evolve(evaluator=datafit,generator=mygenerate,maximize=False, pop_size=pop_size,max_evaluations=max_evaluations,num_elites=1, bounder=inspyred.ec.Bounder(0.,1.),**kwargs) self.pop.sort(reverse=True) self.fits=[ind.fitness for ind in self.pop]
def test_VESManager(showProgress=False): """ run from console with: python -c 'import pygimli.physics.ert.ves as pg; pg.test_VESManager(1)' """ thicks = [2., 10.] res = [100., 5., 30] phi = [0., 20., 0.] # model fails thicks = [2., 6., 10.] res = [100., 500., 20., 800.] phi = [0., 20., 50., 0] synthModel = pg.cat(thicks, res) ab2 = np.logspace(np.log10(1.5), np.log10(100.), 25) mgr = VESManager(verbose=True, debug=False) mgr.fop.setRegionProperties(0, limits=[0.5, 200], trans='log') ra, err = mgr.simulate(synthModel, ab2=ab2, mn2=1.0, noiseLevel=0.01) mgr.exportData('synth.ves', ra, err) mgr.invert(ra, err, nLayer=4, lam=100, showProgress=showProgress) pg.wait() ### Test -- reinit with new parameter count mgr.invert(ra, err, nLayer=3, showProgress=showProgress) #np.testing.assert_array_less(mgr.inv.inv.chi2(), 1) ### Test -- reinit with new data basis ab2 = np.logspace(np.log10(1.5), np.log10(50.), 10) ra, err = mgr.simulate(synthModel, ab2=ab2, mn2=1.0, noiseLevel=0.01) mgr2 = VESManager(verbose=False, debug=False) mgr2.invert(ra, err, nLayer=3, ab2=ab2, mn2=1.0, showProgress=showProgress) #np.testing.assert_array_less(mgr2.inv.inv.chi2(), 1) pg.wait() ### Test -- reinit with complex resistivies mgr.complex = True synthModel = pg.cat(synthModel, phi) ra, err = mgr.simulate(synthModel, ab2=ab2, mn2=1.0, noiseLevel=0.01) mgr.exportData('synthc.ves', ra, err) mgr.invert(ra, err, showProgress=showProgress) np.testing.assert_array_less(mgr.inv.inv.chi2(), 1) if showProgress: print("test done"); pg.wait()
def setData(self, data): self.fop.setData(data) self.dataVals = pg.Vector(0) self.dataErrs = pg.Vector(0) for i, mgr in enumerate(self.mgrs): self.tD.add(mgr.tD, data[i].size()) self.dataVals = pg.cat(self.dataVals, mgr.dataVals(data[i])) self.dataErrs = pg.cat(self.dataErrs, mgr.relErrorVals(data[i])) self.inv.setTransData(self.tD)
def loadData(self, fileName, **kwargs): mat = np.loadtxt(fileName) if len(mat[0]) == 4: self.fop.setDataBasis(ab2=mat[:, 0], mn2=mat[:, 1]) return mat.T if len(mat[0]) == 6: self.complex = True self.fop.setDataBasis(ab2=mat[:, 0], mn2=mat[:, 1]) return mat[:, 0], mat[:, 1], np.array(pg.cat( mat[:, 2], mat[:, 4])), np.array(pg.cat(mat[:, 3], mat[:, 5]))
def loadData(self, fileName, **kwargs): """ Load simple data matrix """ mat = np.loadtxt(fileName) if len(mat[0]) == 4: self.fop.setDataSpace(ab2=mat[:, 0], mn2=mat[:, 1]) return mat.T if len(mat[0]) == 6: self.complex = True self.fop.setDataSpace(ab2=mat[:, 0], mn2=mat[:, 1]) return (mat[:, 0], mat[:, 1], np.array(pg.cat(mat[:, 2], mat[:, 4])), np.array(pg.cat(mat[:, 3], mat[:, 5])))
def fitCCCC(f, amp, phi, error=0.01, lam=10., taupar=(1e-2, 1e-5, 100), cpar=(0.25, 0, 1), mpar=(0, 0, 1)): """Fit complex spectrum by Cole-Cole model based on sigma.""" fCC = ColeColeComplexSigma(f) tLog = pg.RTransLog() fCC.region(0).setStartValue(1. / max(amp)) if mpar[0] == 0: mpar[0] = 1. - min(amp) / max(amp) fCC.region(1).setParameters(*mpar) # m (start,lower,upper) fCC.region(2).setParameters(*taupar) # tau fCC.region(3).setParameters(*cpar) # c data = pg.cat(1. / amp * np.cos(phi), 1. / amp * np.sin(phi)) ICC = pg.RInversion(data, fCC, False) # set up inversion class ICC.setTransModel(tLog) ICC.setAbsoluteError(data * error + max(data) * 0.0001) # perr + ePhi/data) ICC.setLambda(lam) # start with large damping and cool later ICC.setMarquardtScheme(0.8) # lower lambda by 20%/it., no stop chi=1 model = np.asarray(ICC.run()) # run inversion ICC.echoStatus() response = np.asarray(ICC.response()) rRe, rIm = response[:len(f)], response[len(f):] rAmp = 1. / np.sqrt(rRe**2 + rIm**2) return model, rAmp, np.arctan(rIm / rRe)
def createERTData(elecs, schemeName='none', **kwargs): """ Simple data creator for compatibility (advanced version in BERT). Parameters ---------- sounding : bool [False] Create a 1D VES Schlumberger configuration. elecs need to be an array with elecs[0] = mn/2 and elecs[1:] = ab/2. """ if kwargs.pop('sounding', False): data = pg.DataContainerERT() data.setSensors(pg.cat(-elecs[::-1], elecs)) nElecs = len(elecs) for i in range(nElecs - 1): data.createFourPointData(i, i, 2 * nElecs - i - 1, nElecs - 1, nElecs) return data if schemeName != "dd": import pybert as pb # that's bad!!! TODO: remove pybert deps return pb.createData(elecs, schemeName, **kwargs) isClosed = kwargs.pop('closed', False) data = pg.DataContainerERT() data.setSensors(elecs) nElecs = len(elecs) a = [] b = [] m = [] n = [] eb = 0 for i in range(nElecs): for j in range(eb + 2, nElecs): ea = i eb = ea + 1 em = j en = em + 1 if isClosed: en = en % nElecs if en < nElecs and en != ea: a.append(ea) b.append(eb) m.append(em) n.append(en) data.resize(len(a)) data.add('a', a) data.add('b', b) data.add('m', m) data.add('n', n) data.set('valid', np.ones(len(a))) return data
def response_mt(self, model, i=0): model = np.nan_to_num(model) fw, fi, fa, fr = self.fractions(model) rho = self.fpm.rho(fw, fi, fa, fr) s = self.fpm.slowness(fw, fi, fa, fr) print("=" * 30) print(" Min. | Max.") print("-" * 30) print(" Water: %.2f | %.2f" % (np.min(fw), np.max(fw))) print(" Ice: %.2f | %.2f" % (np.min(fi), np.max(fi))) print(" Air: %.2f | %.2f" % (np.min(fa), np.max(fa))) print(" Rock: %.2f | %.2f" % (np.min(fr), np.max(fr))) print("-" * 30) print(" SUM: %.2f | %.2f" % (np.min(fa + fw + fi + fr), np.max(fa + fw + fi + fr))) print("=" * 30) print(" Rho: %.2e | %.2e" % (np.min(rho), np.max(rho))) print(" Vel: %d | %d" % (np.min(1 / s), np.max(1 / s))) t = self.RST.fop.response(s) rhoa = self.ERT.fop.response(rho) return pg.cat(t, rhoa)
def drawSeismogramm(axes, mesh, u, ids, dt, i=None): r"""Extract and show time series from wave field Parameters ---------- """ axes.set_xlim(-20., 20.) axes.set_ylim(0., dt*len(u)*1000) axes.set_aspect(1) axes.set_ylabel('Time in ms') if i is None: i = len(u)-1 t = np.linspace(0, i*dt*1000, i+1) for iw, n in enumerate(ids): pos = mesh.node(n).pos() print(pos) axes.plot(pos[0], 0.05, '^', color='black') trace = pg.cat(pg.RVector(0), u[:(i+1), n]) # print(i+1, n) # print(trace, (max(pg.abs(trace)))) # if max(pg.abs(trace)) > 1e-8: trace *= np.exp(0.5*t) trace /= (max(pg.abs(trace))*1.5) drawWiggle(axes, trace, t=t, xoffset=pos[0]) axes.invert_yaxis()
def drawModel(self, ax, model): nLay = (len(model)+1) // 3 super(VESCModelling, self).drawModel(ax, model[0:nLay*2-1]) pg.mplviewer.drawModel1D(ax=ax, model=pg.cat(model[0:nLay-1], model[nLay*2-1::]), plot='plot', xlabel='Phase [mrad]')
def response(self, par): """Response vector as combined in-phase and out-phase data.""" thk = np.asarray(par[:self.nlay-1], dtype=np.float) res = np.asarray(par[self.nlay-1:2*self.nlay-1], dtype=np.float) mur = np.asarray(par[2*self.nlay-1:3*self.nlay-1], dtype=np.float) + 1 ip, op = self.vmd_hem(self.height, rho=res, d=thk, mur=mur) return pg.cat(ip, op)
def checkErrors(self, err, dataVals): """Return relative error. Default we assume 'err' are relative vales. """ if isinstance(err, pg.DataContainer): rae = None if not err.allNonZero('err'): pg.warn( "Datacontainer have no 'err' values. " "Fallback of 1mV + 3% using ERTManager.estimateError(...) " ) rae = self.estimateError(err, absoluteError=0.001, relativeError=0.03) else: rae = err['err'] if self.fop.complex(): ipe = None if err.haveData('iperr'): amp, phi = pg.utils.toPolar(dataVals) # assuming ipErr are absolute dPhi in mrad ipe = err['iperr'] / abs((phi * 1000)) else: pg.warn("Datacontainer have no 'iperr' values. " "Fallback set to 0.01") ipe = np.ones(err.size()) * 0.01 # pg._y("err", min(rae), max(rae), rae) # pg._y("iperr", min(ipe), max(ipe), ipe) return pg.cat(rae, ipe) return rae
def drawSeismogramm(axes, mesh, u, ids, dt, i=None): r"""Extract and show time series from wave field Parameters ---------- """ axes.set_xlim(-20., 20.) axes.set_ylim(0., dt * len(u) * 1000) axes.set_aspect(1) axes.set_ylabel('Time in ms') if i is None: i = len(u) - 1 t = np.linspace(0, i * dt * 1000, i + 1) for iw, n in enumerate(ids): pos = mesh.node(n).pos() print(pos) axes.plot(pos[0], 0.05, '^', color='black') trace = pg.cat(pg.RVector(0), u[:(i + 1), n]) # print(i+1, n) # print(trace, (max(pg.abs(trace)))) # if max(pg.abs(trace)) > 1e-8: trace *= np.exp(0.5 * t) trace /= (max(pg.abs(trace)) * 1.5) drawWiggle(axes, trace, t=t, xoffset=pos[0]) axes.invert_yaxis()
def __init__(self, fop, data, error, startmodel, lam=20, beta=10000, maxIter=50, fwmin=0, fwmax=1, fimin=0, fimax=1, famin=0, famax=1, frmin=0, frmax=1): LSQRInversion.__init__(self, data, fop, verbose=True, dosave=True) self._error = pg.Vector(error) # Set data transformations self.logtrans = pg.trans.TransLog() self.trans = pg.trans.Trans() self.dcumtrans = pg.trans.TransCumulative() self.dcumtrans.add(self.trans, fop.RST.data.size()) self.dcumtrans.add(self.logtrans, fop.ERT.data.size()) self.setTransData(self.dcumtrans) # Set model transformation n = fop.cellCount self.mcumtrans = pg.trans.TransCumulative() self.transforms = [] phase_limits = [[fwmin, fwmax], [fimin, fimax], [famin, famax], [frmin, frmax]] for i, (lower, upper) in enumerate(phase_limits): if lower == 0: lower = 0.001 self.transforms.append(pg.trans.TransLogLU(lower, upper)) self.mcumtrans.add(self.transforms[i], n) self.setTransModel(self.mcumtrans) # Set error self.setRelativeError(self._error) # Set some defaults # Set maximum number of iterations (default is 20) self.setMaxIter(maxIter) # Regularization strength self.setLambda(lam) self.setDeltaPhiAbortPercent(0.25) # fop = self.forwardOperator() fop.createConstraints() # Important! ones = pg.Vector(fop._I.rows(), 1.0) phiVec = pg.cat(ones, startmodel) self.setParameterConstraints(fop._G, phiVec, beta) self.setModel(startmodel)
def inv2D(self, nlay, lam=100., resL=1., resU=1000., thkL=1., thkU=100., minErr=1.0): """2d LCI inversion class.""" if isinstance(nlay, int): modVec = pg.RVector(nlay * 2 - 1, 30.) cType = 0 # no reference model else: modVec = nlay cType = 10 # use this as referencemodel nlay = (len(modVec) + 1) / 2 # init forward operator self.f2d = self.FOP2d(nlay) # transformations self.transData = pg.RTrans() self.transThk = pg.RTransLogLU(thkL, thkU) self.transRes = pg.RTransLogLU(resL, resU) for i in range(nlay - 1): self.f2d.region(i).setTransModel(self.transThk) for i in range(nlay - 1, nlay * 2 - 1): self.f2d.region(i).setTransModel(self.transRes) # set constraints self.f2d.region(0).setConstraintType(cType) self.f2d.region(1).setConstraintType(cType) # collect data vector datvec = pg.RVector(0) for i in range(len(self.x)): datvec = pg.cat(datvec, self.datavec(i)) # collect error vector if self.ERR is None: error = 1.0 else: error = [] for i in range(len(self.x)): err = np.maximum(self.ERR[i][self.activeFreq] * 0.701, minErr) error.extend(err) # generate starting model by repetition model = pg.asvector(np.repeat(modVec, len(self.x))) INV = pg.RInversion(datvec, self.f2d, self.transData) INV.setAbsoluteError(error) INV.setLambda(lam) INV.setModel(model) INV.setReferenceModel(model) return INV
def response(self, model): """ cut-together forward responses of all soundings """ modA = np.reshape(model, (self.nx, self.nlay*2-1)) resp = pg.RVector(0) for i, modi in enumerate(modA): resp = pg.cat(resp, self.FOP1d[i].response(modi)) return resp
def response(self, model): """ yields forward model response """ modA = np.asarray(model).reshape((self.nlay * 2 - 1, self.nx)).T resp = pg.RVector(0) for modi in modA: resp = pg.cat(resp, self.FOP1d.response(modi)) return resp
def response(self, model): """ cut-together forward responses of all soundings """ modA = np.asarray(model).reshape((self.nlay * 2 - 1, self.nx)).T resp = pg.RVector(0) for modi in modA: resp = pg.cat(resp, self.FOP.response(modi)) return resp
def response(self, model): """Response as pasted forward responses from all soundings.""" modA = np.reshape(model, (self.nx, self.nlay*2-1)) resp = pg.RVector(0) for i, modi in enumerate(modA): resp = pg.cat(resp, self.FOP1d[i].response(modi)) return resp
def block1dInversion(self, nlay=2, lam=100., show=False, verbose=True, uncertainty=False): """Invert all data together by a 1D model (more general solution).""" data, error = pg.Vector(), pg.Vector() for mrs in self.mrs: data = pg.cat(data, mrs.data) error = pg.cat(error, np.real(mrs.error)) # f = JointMRSModelling(self.mrs, nlay) f = MultiFOP(self.mrs, nlay) mrsobj = self.mrs[0] for i in range(3): f.region(i).setParameters(mrsobj.startval[i], mrsobj.lowerBound[i], mrsobj.upperBound[i]) INV = pg.Inversion(data, f, verbose) INV.setLambda(lam) INV.setMarquardtScheme(0.8) # INV.stopAtChi1(False) # should be already in MarquardtScheme INV.setDeltaPhiAbortPercent(0.5) INV.setAbsoluteError(error) model = INV.run() m0 = self.mrs[0] m0.model = np.asarray(model) if uncertainty: from pygimli.utils import iterateBounds m0.modelL, m0.modelU = iterateBounds(INV, dchi2=INV.chi2() / 2, change=1.2) if show: self.show1dModel() # %% fill up 2D model (for display only) self.WMOD, self.TMOD = [], [] thk = model[0:nlay - 1] wc = model[nlay - 1:2 * nlay - 1] t2 = model[2 * nlay - 1:3 * nlay - 1] for i in range(len(self.mrs)): self.WMOD.append(np.hstack((thk, wc))) self.TMOD.append(np.hstack((thk, t2))) return model
def response(self, par): ip, op = self.vmd_hem(self.height, np.asarray(par)[self.nlay-1:self.nlay*2-1], np.asarray(par)[:self.nlay-1]) # ip, op = self.vmd_hem(self.height, # np.asarray(par(self.nlay-1, self.nlay*2-1)), # np.asarray(par(0, self.nlay-1))) return pg.cat(ip, op)
def createDefaultStartModel(self): """ """ res = pb.getComplexData(self.data()) parCount = self.regionManager().parameterCount() re = pg.Vector(parCount, pg.mean(pg.math.real(res))) im = pg.Vector(parCount, -pg.mean(pg.math.imag(res))) return pg.cat(re, im)
def createDefaultStartModel(self): """ """ res = pb.getComplexData(self.data()) parCount = self.regionManager().parameterCount() re = pg.RVector(parCount, pg.mean(pg.real(res))) im = pg.RVector(parCount, -pg.mean(pg.imag(res))) return pg.cat(re, im)
def squeezeComplex(z, polar=False, conj=False): """Squeeze complex valued array into [real, imag] or [amp, phase(rad)]""" if isinstance(z, (pg.matrix.CSparseMapMatrix, pg.matrix.CSparseMatrix, pg.matrix.CMatrix)): return toRealMatrix(z, conj=conj) if isComplex(z): vals = np.array(z) if conj: vals = np.conj(vals) if polar is True: vals = pg.cat(*toPolar(z)) else: vals = pg.cat(vals.real, vals.imag) return vals return z
def response(self, model): """Cut together forward responses of all soundings.""" modA = np.reshape(model, (self.nx, self.np)) resp = pg.RVector(0) for i, modi in enumerate(modA): resp = pg.cat(resp, self.FOP1d[i].response(modi)) return resp
def response(self, par): """Response vector as combined in-phase and out-phase data.""" thk = np.asarray(par[:self.nlay - 1], dtype=np.float) res = np.asarray(par[self.nlay - 1:2 * self.nlay - 1], dtype=np.float) mur = np.asarray(par[2 * self.nlay - 1:3 * self.nlay - 1], dtype=np.float) + 1 ip, op = self.vmd_hem(self.height, rho=res, d=thk, mur=mur) return pg.cat(ip, op)
def response(self, model): """Response as pasted forward responses from all soundings.""" modA = np.reshape(model, (self.nx, self.nlay * 2 - 1)) resp = pg.RVector(0) for i, modi in enumerate(modA): resp = pg.cat(resp, self.FOP1d[i].response(modi)) return resp
def response(self, model): """Cut together forward responses of all soundings.""" modA = np.asarray(model).reshape((self.nlay * 2 - 1, self.nx)).T resp = pg.Vector(0) for modi in modA: resp = pg.cat(resp, self.FOP.response(modi)) return resp
def response(self, model): """Yields forward model response.""" modA = np.asarray(model).reshape((self.nlay * 2 - 1, self.nx)).T resp = pg.Vector(0) for modi in modA: resp = pg.cat(resp, self.FOP1d.response(modi)) return resp
def getIntegrationWeights(self, rMin, rMax): """Retrieve Gauss-Legende/Laguerre integration weights.""" nGauLegendre = max(int((6.0 * np.log10(rMax / rMin))), 4) nGauLaguerre = 4 k = pg.RVector() w = pg.RVector() k0 = 1.0 / (2.0 * rMin) pg.GaussLegendre(0.0, 1.0, nGauLegendre, k, w) kLeg = k0 * k * k wLeg = 2.0 * k0 * k * w / np.pi pg.GaussLaguerre(nGauLaguerre, k, w) kLag = k0 * (k + 1.0) wLag = k0 * np.exp(k) * w / np.pi return pg.cat(kLeg, kLag), pg.cat(wLeg, wLag)
def createInv(self, nlay, lam=100., errVES=3, verbose=True): """Create Marquardt type inversion instance with data transformatio""" self.createFOP(nlay) self.tMod = pg.RTransLog() self.tMRS = pg.RTrans() self.tVES = pg.RTransLog() self.transData = pg.RTransCumulative() self.transData.push_back(self.tMRS, len(self.data)) self.transData.push_back(self.tVES, len(self.rhoa)) data = pg.cat(self.data, self.rhoa) self.INV = pg.RInversion(data, self.f, self.transData, verbose) self.INV.setLambda(lam) self.INV.setMarquardtScheme(0.8) self.INV.stopAtChi1(False) # now in MarquardtScheme self.INV.setDeltaPhiAbortPercent(0.5) # self.INV.setMaxIter(1) error = pg.cat(self.error, self.rhoa * errVES / 100.) self.INV.setAbsoluteError(error)
def loadData(self, fileName, **kwargs): mat = np.loadtxt(fileName) if len(mat[0]) == 4: self.fop.setDataBasis(ab2=mat[:,0], mn2=mat[:,1]) return mat.T if len(mat[0]) == 6: self.complex = True self.fop.setDataBasis(ab2=mat[:,0], mn2=mat[:,1]) return mat[:,0], mat[:,1], np.array(pg.cat(mat[:,2], mat[:,4])), np.array(pg.cat(mat[:,3], mat[:,5]))
def createInv(self,nlay,lam=100.,errVES=3,verbose=True): """ create marquardt type inversion instance with data transformation """ self.createFOP(nlay) self.tMod = pg.RTransLog() self.tMRS = pg.RTrans() self.tVES = pg.RTransLog() self.transData = pg.RTransCumulative() self.transData.push_back( self.tMRS, len(self.data) ) self.transData.push_back( self.tVES, len(self.rhoa) ) data = pg.cat(self.data, self.rhoa) self.INV = pg.RInversion(data, self.f, self.transData, verbose) self.INV.setLambda(lam) self.INV.setMarquardtScheme(0.8) self.INV.stopAtChi1(False) # now in MarquardtScheme self.INV.setDeltaPhiAbortPercent(0.5) # self.INV.setMaxIter(1) error = pg.cat(self.error, self.rhoa*errVES/100.) self.INV.setAbsoluteError(error)
def genMods( individual ): """ generate MRS and VES models from unit vector """ model = pg.asvector( individual ) * ( self.lUB - self.lLB ) + self.lLB if self.logpar: model = pg.exp( model ) modMRS = model(0,nlay*3-1) modVES = pg.cat(model(0,nlay-1),model(nlay*3-1,nlay*4-1)) return modMRS, modVES
def response(self, model): """ """ modA = np.asarray(model).reshape((self.nlay_*2-1,self.nx_)).T resp = pg.RVector(0) for modi in modA: resp = pg.cat(resp, self.FOP_.response(modi)) return resp
def response(self, par): """Compute response vector by pasting in-phase and out-phase data.""" ip, op = self.vmd_hem(self.height, np.asarray(par)[self.nlay-1:self.nlay*2-1], np.asarray(par)[:self.nlay-1]) # ip, op = self.vmd_hem(self.height, # np.asarray(par(self.nlay-1, self.nlay*2-1)), # np.asarray(par(0, self.nlay-1))) return pg.cat(ip, op)
def response(self, par): """Compute response vector by pasting in-phase and out-phase data.""" ip, op = self.vmd_hem(self.height, np.asarray(par)[self.nlay - 1:self.nlay * 2 - 1], np.asarray(par)[:self.nlay - 1]) # ip, op = self.vmd_hem(self.height, # np.asarray(par(self.nlay-1, self.nlay*2-1)), # np.asarray(par(0, self.nlay-1))) return pg.cat(ip, op)
def inv2D(self, nlay, lam=100., resL=1., resU=1000., thkL=1., thkU=100., minErr=1.0): """ 2d LCI inversion class """ if isinstance(nlay, int): modVec = pg.RVector(nlay * 2 - 1, 30.) cType = 0 # no reference model else: modVec = nlay cType = 10 # use this as referencemodel nlay = (len(modVec) + 1) / 2 # init forward operator self.f2d = self.FOP2d(nlay) # transformations self.tD = pg.RTrans() self.tThk = pg.RTransLogLU(thkL, thkU) self.tRes = pg.RTransLogLU(resL, resU) for i in range(nlay - 1): self.f2d.region(i).setTransModel(self.tThk) for i in range(nlay - 1, nlay * 2 - 1): self.f2d.region(i).setTransModel(self.tRes) # set constraints self.f2d.region(0).setConstraintType(cType) self.f2d.region(1).setConstraintType(cType) # collect data vector datvec = pg.RVector(0) for i in range(len(self.x)): datvec = pg.cat(datvec, self.datavec(i)) # collect error vector if self.ERR is None: error = 1.0 else: error = [] for i in range(len(self.x)): err = np.maximum(self.ERR[i][self.activeFreq] * 0.701, minErr) error.extend(err) # generate starting model by repetition model = pg.asvector(np.repeat(modVec, len(self.x))) INV = pg.RInversion(datvec, self.f2d, self.tD) INV.setAbsoluteError(error) INV.setLambda(lam) INV.setModel(model) INV.setReferenceModel(model) return INV
def fitDebyeModel(self, ePhi=0.001, lam=1e3, lamFactor=0.8, mint=None, maxt=None, nt=None, new=True, showFit=False, cType=1): """fit a (smooth) continuous Debye model (Debye decomposition)""" nf = len(self.f) if mint is None: mint = .1 / max(self.f) if maxt is None: maxt = .5 / min(self.f) if nt is None: nt = nf*2 # %% discretize tau, setup DD and perform DD inversion self.tau = np.logspace(log10(mint), log10(maxt), nt) phi = self.phi tLin, tLog, tM = pg.RTrans(), pg.RTransLog(), pg.RTransLogLU(0., 1.) if new: reNorm, imNorm = self.zNorm() fDD = DebyeComplex(self.f, self.tau) Znorm = pg.cat(reNorm, imNorm) IDD = pg.RInversion(Znorm, fDD, tLog, tM, False) IDD.setAbsoluteError(max(Znorm)*0.003+0.01) else: fDD = DebyePhi(self.f, self.tau) IDD = pg.RInversion(phi, fDD, tLin, tM, True) IDD.setAbsoluteError(ePhi) # 1 mrad fDD.regionManager().setConstraintType(cType) IDD.stopAtChi1(False) startModel = pg.RVector(nt, 0.01) IDD.setModel(startModel) IDD.setLambda(lam) IDD.setLambdaFactor(lamFactor) self.mDD = IDD.run() IDD.echoStatus() if new: resp = np.array(IDD.response()) respRe = resp[:nf] respIm = resp[nf:] respC = ((1 - respRe) + respIm * 1j) * max(self.amp) self.phiDD = np.angle(respC) self.ampDD = np.abs(respC) if showFit: fig, ax = self.showData(znorm=True, nrows=3) ax[0].plot(self.f, respRe, 'r-') ax[1].plot(self.f, respIm, 'r-') ax[2].semilogx(self.tau, self.mDD, 'r-') ax[2].set_xlim(max(self.tau), min(self.tau)) ax[2].set_ylim(0., max(self.mDD)) ax[2].grid(True) ax[2].set_xlabel(r'$\tau$ [s]') ax[2].set_xlabel('$m$ [-]') else: self.phiDD = IDD.response() if showFit: fig, ax = self.showData(nrows=3) ax[2].semilogx(self.tau, self.mDD, 'r-')
def blockLCInversion(self, nlay=2, startModel=None, **kwargs): """Laterally constrained (piece-wise 1D) block inversion.""" data, error, self.nData = pg.Vector(), pg.Vector(), [] for mrs in self.mrs: data = pg.cat(data, mrs.data) error = pg.cat(error, mrs.error) self.nData.append(len(mrs.data)) fop = MRSLCI(self.mrs, nlay=nlay) fop.region(0).setZWeight(kwargs.pop('zWeight', 0)) fop.region(0).setConstraintType(kwargs.pop('cType', 1)) transData, transMod = pg.trans.Trans(), pg.trans.TransLog( ) # LU(1., 500.) if startModel is None: startModel = self.block1dInversion(nlay, verbose=False) model = kwargs.pop('startvec', np.tile(startModel, len(self.mrs))) INV = pg.Inversion(data, fop, transData, transMod, True, False) INV.setModel(model) INV.setReferenceModel(model) INV.setAbsoluteError(error) INV.setLambda(kwargs.pop('lam', 100)) INV.setMaxIter(kwargs.pop('maxIter', 20)) # INV.stopAtChi1(False) INV.setLambdaFactor(0.9) INV.setDeltaPhiAbortPercent(0.1) model = INV.run() self.WMOD, self.TMOD = [], [] for par in np.reshape(model, (len(self.mrs), 3 * nlay - 1)): thk = par[0:nlay - 1] self.WMOD.append(np.hstack((thk, par[nlay - 1:2 * nlay - 1]))) self.TMOD.append(np.hstack((thk, par[2 * nlay - 1:3 * nlay - 1]))) ind = np.hstack((0, np.cumsum(self.nData))) resp = INV.response() misfit = data - resp emisfit = misfit / error misfit *= 1e9 self.totalChi2 = INV.chi2() self.totalRMS = INV.absrms() * 1e9 self.RMSvec, self.Chi2vec = [], [] for i in range(len(self.mrs)): self.RMSvec.append(np.sqrt(np.mean(misfit[ind[i]:ind[i + 1]]**2))) self.Chi2vec.append(np.mean(emisfit[ind[i]:ind[i + 1]]**2))
def blockLCInversion(self, nlay=2, startModel=None, **kwargs): """Laterally constrained (piece-wise 1D) block inversion.""" data, error, self.nData = pg.RVector(), pg.RVector(), [] for mrs in self.mrs: data = pg.cat(data, mrs.data) error = pg.cat(error, mrs.error) self.nData.append(len(mrs.data)) fop = MRSLCI(self.mrs, nlay=nlay) fop.region(0).setZWeight(kwargs.pop('zWeight', 0)) fop.region(0).setConstraintType(kwargs.pop('cType', 1)) transData, transMod = pg.RTrans(), pg.RTransLog() # LU(1., 500.) if startModel is None: startModel = self.block1dInversion(nlay, verbose=False) model = kwargs.pop('startvec', np.tile(startModel, len(self.mrs))) INV = pg.RInversion(data, fop, transData, transMod, True, False) INV.setModel(model) INV.setReferenceModel(model) INV.setAbsoluteError(error) INV.setLambda(kwargs.pop('lam', 100)) INV.setMaxIter(kwargs.pop('maxIter', 20)) # INV.stopAtChi1(False) INV.setLambdaFactor(0.9) INV.setDeltaPhiAbortPercent(0.1) model = INV.run() self.WMOD, self.TMOD = [], [] for par in np.reshape(model, (len(self.mrs), 3*nlay-1)): thk = par[0:nlay-1] self.WMOD.append(np.hstack((thk, par[nlay-1:2*nlay-1]))) self.TMOD.append(np.hstack((thk, par[2*nlay-1:3*nlay-1]))) ind = np.hstack((0, np.cumsum(self.nData))) resp = INV.response() misfit = data - resp emisfit = misfit / error misfit *= 1e9 self.totalChi2 = INV.chi2() self.totalRMS = INV.absrms()*1e9 self.RMSvec, self.Chi2vec = [], [] for i in range(len(self.mrs)): self.RMSvec.append(np.sqrt(np.mean(misfit[ind[i]:ind[i+1]]**2))) self.Chi2vec.append(np.mean(emisfit[ind[i]:ind[i+1]]**2))
def checkError(self, err, data=None): """Collect error values.""" if len(err) != len(self.mgrs): pg.critical("Please provide data for all managers") vals = pg.Vector(0) for i, mgr in enumerate(self.mgrs): # we get the data values again or we have to split data dataVals = mgr.checkData(self.fop._data[i]) vals = pg.cat(vals, mgr.checkError(err[i], dataVals)) return vals
def genMods(individual): """generate MRS and VES models from unit vector""" model = individual * (self.lUB - self.lLB) + self.lLB # model = pg.asvector(individual) * (self.lUB - self.lLB) + self.lLB if self.logpar: model = pg.exp(model) modMRS = model(0, nlay * 3 - 1) modVES = pg.cat(model(0, nlay - 1), model(nlay * 3 - 1, nlay * 4 - 1)) return modMRS, modVES
def response(self, par): """Cut together forward responses of all soundings.""" mods = np.asarray(par).reshape(self._nSoundings, self._parPerSounding) resp = pg.Vector(0) for i in range(self._nSoundings): r = self._fops1D[i].response(mods[i]) #print("i:", i, mods[i], r) resp = pg.cat(resp, r) return resp
def prepare(self, dataVals, errorVals, nLayers=4, **kwargs): dataVec = pg.RVector() for d in dataVals: dataVec = pg.cat(dataVec, d) errVec = pg.RVector() for e in errorVals: errVec = pg.cat(errVec, e) self.fop.initJacobian(dataVals=dataVals, nLayers=nLayers, nPar=kwargs.pop('nPar', None)) ### self.fop.initJacobian resets prior set startmodels if self._startModel is not None: self.fop.setStartModel(self._startModel) rC = self.fop.regionManager().regionCount() if kwargs.pop('disableLCI', False): self.inv.setMarquardtScheme(0.7) #self.inv.setLocalRegularization(True) for r in self.fop.regionManager().regionIdxs(): self.fop.setRegionProperties(r, cType=0) else: #self.inv.stopAtChi1(False) cType = kwargs.pop('cType', None) if cType is None: cType = [1] * rC zWeight = kwargs.pop('zWeight', None) if zWeight is None: zWeight = [0.0] * rC self.fop.setRegionProperties('*', cType=cType, zWeight=zWeight, **kwargs) self.inv.setReferenceModel(self.fop.startModel()) return dataVec, errVec
def block1dInversionNew(self, nlay=2, lam=100., verbose=True): """invert all data together by a 1D model (more general solution)""" data, error = pg.RVector(), pg.RVector() for mrs in self.mrs: data = pg.cat(data, mrs.data) error = pg.cat(error, mrs.error) f = JointMRSModelling(self.mrs, nlay) mrsobj = self.mrs[0] for i in range(3): f.region(i).setParameters(mrsobj.startval[i], mrsobj.lowerBound[i], mrsobj.upperBound[i]) INV = pg.RInversion(data, f, verbose) INV.setLambda(lam) INV.setMarquardtScheme(0.8) # INV.stopAtChi1(False) # should be already in MarquardtScheme INV.setDeltaPhiAbortPercent(0.5) INV.setAbsoluteError(error) model = INV.run() return model
def block1dInversion(self, nlay=2, lam=100., show=False, verbose=True, uncertainty=False): """Invert all data together by a 1D model (more general solution).""" data, error = pg.RVector(), pg.RVector() for mrs in self.mrs: data = pg.cat(data, mrs.data) error = pg.cat(error, np.real(mrs.error)) # f = JointMRSModelling(self.mrs, nlay) f = MultiFOP(self.mrs, nlay) mrsobj = self.mrs[0] for i in range(3): f.region(i).setParameters(mrsobj.startval[i], mrsobj.lowerBound[i], mrsobj.upperBound[i]) INV = pg.RInversion(data, f, verbose) INV.setLambda(lam) INV.setMarquardtScheme(0.8) # INV.stopAtChi1(False) # should be already in MarquardtScheme INV.setDeltaPhiAbortPercent(0.5) INV.setAbsoluteError(error) model = INV.run() m0 = self.mrs[0] m0.model = np.asarray(model) if uncertainty: from pygimli.utils import iterateBounds m0.modelL, m0.modelU = iterateBounds( INV, dchi2=INV.chi2() / 2, change=1.2) if show: self.show1dModel() # %% fill up 2D model (for display only) self.WMOD, self.TMOD = [], [] thk = model[0:nlay-1] wc = model[nlay-1:2*nlay-1] t2 = model[2*nlay-1:3*nlay-1] for i in range(len(self.mrs)): self.WMOD.append(np.hstack((thk, wc))) self.TMOD.append(np.hstack((thk, t2))) return model
def fitCCC(f, amp, phi, eRho=0.01, ePhi=0.001, lam=1000., mstart=None, taupar=(1e-2, 1e-5, 100), cpar=(0.5, 0, 1)): """Fit complex spectrum by Cole-Cole model.""" fCC = ColeColeComplex(f) tLog = pg.RTransLog() fCC.region(0).setStartValue(max(amp)) if mstart is None: # compute from amplitude decay mstart = 1. - min(amp) / max(amp) fCC.region(1).setParameters(mstart, 0, 1) # m (start,lower,upper) fCC.region(2).setParameters(*taupar) # tau fCC.region(3).setParameters(*cpar) # c data = pg.cat(amp, phi) ICC = pg.RInversion(data, fCC, False) # set up inversion class ICC.setTransModel(tLog) error = pg.cat(eRho*amp, pg.RVector(len(f), ePhi)) ICC.setAbsoluteError(error) # perr + ePhi/data) ICC.setLambda(lam) # start with large damping and cool later ICC.setMarquardtScheme(0.8) # lower lambda by 20%/it., no stop chi=1 model = np.asarray(ICC.run()) # run inversion ICC.echoStatus() response = np.asarray(ICC.response()) return model, response[:len(f)], response[len(f):]
def blockLCInversion(self, nlay=2, startModel=None, lam=100.0, cType=1): """laterally constrained (piece-wise 1D) block inversion""" data, error, self.nData = pg.RVector(), pg.RVector(), [] for mrs in self.mrs: data = pg.cat(data, mrs.data) error = pg.cat(error, mrs.error) self.nData.append(len(mrs.data)) fop = MRSLCI(self.mrs, nlay=nlay) fop.region(0).setZWeight(0.01) fop.region(0).setConstraintType(cType) transData, transMod = pg.RTrans(), pg.RTransLog() # LU(1., 500.) if startModel is None: startModel = self.block1dInversion(nlay, verbose=False) model = np.tile(startModel, len(self.mrs)) INV = pg.RInversion(data, fop, transData, transMod, True, False) INV.setModel(model) INV.setReferenceModel(model) INV.setAbsoluteError(error) INV.setLambda(lam) INV.stopAtChi1(False) model = INV.run() self.WMOD, self.TMOD = [], [] for par in np.reshape(model, (len(self.mrs), nlay * 3 - 1)): thk = par[0 : nlay - 1] self.WMOD.append(np.hstack((thk, par[nlay - 1 : 2 * nlay - 1]))) self.TMOD.append(np.hstack((thk, par[2 * nlay - 1 : 3 * nlay - 1]))) ind = np.hstack((0, np.cumsum(self.nData))) resp = INV.response() misfit = data - resp emisfit = misfit / error misfit *= 1e9 self.RMSvec, self.Chi2vec = [], [] for i in range(len(self.mrs)): self.RMSvec.append(np.sqrt(np.mean(misfit[ind[i] : ind[i + 1]] ** 2))) self.Chi2vec.append(np.mean(emisfit[ind[i] : ind[i + 1]] ** 2))
def createStartModel(self, rhoa, nLayer, thickness=None): r"""Create suitable starting model. Create suitable starting model based on median apparent resistivity values and skin depth approximation. """ res = np.ones(nLayer) * pg.median(rhoa) if thickness is None: skinDepth = np.sqrt(max(self.t) * pg.median(rhoa)) * 500 thk = np.arange(nLayer) / sum(np.arange(nLayer)) * skinDepth / 2. thk = thk[1:] else: thk = np.ones(nLayer-1) * thickness self.setStartModel(pg.cat(thk, res)) return self.startModel()
def runEMO(self,nlay=5,pop_size=100,max_generations=100): """ run evolutionary multi-objective optimization (EMO) using inspyred for now fixed to NSGA-II algorithm after Deb (2002) (non-dominated sorting genetic algorithm) """ import inspyred def genMods( individual ): """ generate MRS and VES models from unit vector """ model = pg.asvector( individual ) * ( self.lUB - self.lLB ) + self.lLB if self.logpar: model = pg.exp( model ) modMRS = model(0,nlay*3-1) modVES = pg.cat(model(0,nlay-1),model(nlay*3-1,nlay*4-1)) return modMRS, modVES def mygenerate( random, args ): """ generate a random vector of model size """ return [random.random() for i in range( nlay*4 - 1 )] @inspyred.ec.evaluators.evaluator def datafit( individual, args ): """ return data fits for MRS and VES as Pareto object """ modMRS, modVES = genMods(individual) MRSmisfit = ( self.data - self.f(modMRS) ) / self.error VESmisfit = ( np.log(self.rhoa) - np.log(self.fVES(modVES)) ) / np.log(1.02) return inspyred.ec.emo.Pareto([np.mean(MRSmisfit**2),np.mean(VESmisfit**2)]) self.createFOP(nlay) self.fVES = pg.DC1dModelling(nlay,self.ab2,self.mn2) lowerBound = pg.cat( pg.cat( pg.RVector(nlay-1,self.lowerBound[0]), pg.RVector(nlay,self.lowerBound[1])), pg.cat( pg.RVector(nlay,self.lowerBound[2]), pg.RVector(nlay,self.lowerBound[3]) ) ) upperBound = pg.cat( pg.cat( pg.RVector(nlay-1,self.upperBound[0]), pg.RVector(nlay,self.upperBound[1])), pg.cat( pg.RVector(nlay,self.upperBound[2]), pg.RVector(nlay,self.upperBound[3]) ) ) if self.logpar: self.lLB, self.lUB = pg.log(lowerBound), pg.log(upperBound) # ready mapping functions else: self.lLB, self.lUB = lowerBound, upperBound rand = random.Random() rand.seed(int(time.time())) ea = inspyred.ec.emo.NSGA2(rand) ea.variator = [inspyred.ec.variators.blend_crossover, inspyred.ec.variators.gaussian_mutation] ea.terminator = inspyred.ec.terminators.generation_termination ea.observer = [inspyred.ec.observers.stats_observer, inspyred.ec.observers.file_observer] self.pop = ea.evolve(evaluator=datafit, generator=mygenerate, maximize=False, bounder=inspyred.ec.Bounder(0.,1.), pop_size=pop_size, max_generations=max_generations)
def drawSeismogramm(ax, mesh, u, dt, ids=None, pos=None, i=None): r"""Extract and show time series from wave field Parameters ---------- ids: list List of node ids for the given mesh. pos : list List of positions for the given mesh. We will look for the nearest node. """ ax.set_xlim(mesh.xmin(), mesh.xmax()) ax.set_ylim(0., dt*len(u)*1000) ax.set_aspect(1) ax.set_ylabel('Time (ms)') ax.set_xlabel('Distance (m)') if i is None: i = len(u)-1 t = np.linspace(0, i*dt*1000, i+1) if ids is None and pos is not None: ids = [] for p in pos: ids.append(mesh.findNearestNode(p)) xDist = mesh.node(0).pos().distance(mesh.node(1).pos()) for iw, n in enumerate(ids): pos = mesh.node(n).pos() ax.plot(pos[0], 0.05, '^', color='black') trace = pg.cat(pg.RVector(0), u[:(i+1), n]) # print(i+1, n) # print(trace, (max(pg.abs(trace)))) # if max(pg.abs(trace)) > 1e-8: trace *= np.exp(1/1000 * t) trace /= (max(pg.abs(trace))) trace *= 10 drawWiggle(ax, trace, t=t, xoffset=pos[0]) ax.invert_yaxis()