コード例 #1
0
ファイル: exdelays.py プロジェクト: ahesford/habis-tools
def exdelayEngine(config):
    '''
	Use positions of elements and reflectors specified in the provided
	config, combined with a specified sound speed and reflector radius, to
	estimate the round-trip arrival times from every element to the
	reflector and back.
	'''
    msection = 'measurement'
    esection = 'exdelays'
    try:
        # Try to grab the input and output files
        eltfiles = config.getlist(esection, 'elements')
        rflfile = config.get(esection, 'reflectors')
        timefile = config.get(esection, 'timefile')
    except Exception as e:
        err = 'Configuration must specify elements, reflectors and timefile in [%s]' % esection
        raise HabisConfigError.fromException(err, e)

    # Grab the sound speed and reflector radius
    try:
        c = config.get(msection, 'c', mapper=float)
        r = config.get(msection, 'radius', mapper=float)
    except Exception as e:
        err = 'Configuration must specify c and radius in [%s]' % msection
        raise HabisConfigError.fromException(err, e)

    try:
        # Read an optional global time offset
        offset = config.get(esection, 'offset', mapper=float, default=0.)
    except Exception as e:
        err = 'Invalid optional offset in [%s]' % esection
        raise HabisConfigError.fromException(err, e)

    # Read the element and reflector positions
    eltspos = dict(kp for efile in eltfiles
                   for kp in loadkeymat(efile).items())
    reflpos = np.loadtxt(rflfile, ndmin=2)
    nrefl, nrdim = reflpos.shape

    times = {}
    for elt, epos in eltspos.items():
        nedim = len(epos)
        if not nedim <= nrdim <= nedim + 2:
            raise ValueError(
                'Incompatible reflector and element dimensionalities')
        # Determine one-way distances between element and reflector centers
        dx = norm(epos[np.newaxis, :] - reflpos[:, :nedim], axis=-1)
        # Use encoded wave speed if possible, otherwise use global speed
        try:
            lc = reflpos[:, nedim]
        except IndexError:
            lc = c
        # Use encoded radius if possible, otherwise use global radius
        try:
            lr = reflpos[:, nedim + 1]
        except IndexError:
            lr = r
        # Convert distances to round-trip arrival times
        times[elt, elt] = 2 * (dx - lr) / lc + offset

    # Save the estimated arrival times
    savez_keymat(timefile, times)
コード例 #2
0
ファイル: pathtimes.py プロジェクト: ahesford/habis-tools
    else:
        # Store only the local portion for accumulation
        rows = len(times)
        alltimes, displs = None, None

    # Build an MPI datatype for the record accumulation
    offsets = [timetype.fields[n][1] for n in timetype.names]
    mtypes = [MPI.LONG] * 2 + [MPI.DOUBLE] * 1
    mptype = MPI.Datatype.Create_struct([1] * len(mtypes), offsets, mtypes)
    mptype.Commit()

    # Accumulate all records on the root node
    comm.Gatherv([times, ntimes, mptype], [alltimes, counts, displs, mptype])
    # No need for MPI datatype anymore
    mptype.Free()

    if not rank:
        if args.trlist:
            # Convert the record array to a keymap and save
            times = {(t, r): tm for (t, r, tm) in alltimes if not np.isnan(tm)}
            savez_keymat(args.output, times)
        else:
            # Find indices to sort on transmit-receive pair
            indx = np.lexsort((alltimes['r'], alltimes['t']))
            # Rearrange the array according to the sort order
            np.take(alltimes, indx, out=alltimes)
            # Write the values, ignoring indices
            b = alltimes.view('float64').reshape(alltimes.shape + (-1, ))[:,
                                                                          2:]
            mio.writebmat(b.astype('float32'), args.output)
コード例 #3
0
ファイル: atimes.py プロジェクト: ahesford/habis-tools
def atimesEngine(config):
    '''
	Use habis.trilateration.ArrivalTimeFinder to determine a set of
	round-trip arrival times from a set of one-to-many multipath arrival
	times. Multipath arrival times are computed as the maximum of
	cross-correlation with a reference pulse, plus some constant offset.
	'''
    asec = 'atimes'
    msec = 'measurement'
    ssec = 'sampling'

    kwargs = {}

    def _throw(msg, e, sec=None):
        if not sec: sec = asec
        raise HabisConfigError.fromException(f'{msg} in [{sec}]', e)

    try:
        # Read all target input lists
        targets = sorted(k for k in config.options(asec)
                         if k.startswith('target'))
        targetfiles = OrderedDict()
        for target in targets:
            targetfiles[target] = matchfiles(config.getlist(asec, target))
            if len(targetfiles[target]) < 1:
                raise HabisConfigError(f'Key {target} matches no inputs')
    except Exception as e:
        _throw('Configuration must specify at least one unique "target" key',
               e)

    try:
        efiles = config.getlist(asec, 'elements', default=None)
        if efiles:
            efiles = matchfiles(efiles)
            kwargs['elements'] = loadmatlist(efiles, nkeys=1)
    except Exception as e:
        _throw('Invalid optional elements', e)

    # Grab the reference file
    try:
        reffile = config.get(msec, 'reference', default=None)
    except Exception as e:
        _throw('Invalid optional reference', e, msec)

    # Grab the output file
    try:
        outfile = config.get(asec, 'outfile')
    except Exception as e:
        _throw('Configuration must specify outfile', e)

    try:
        # Grab the number of processes to use (optional)
        nproc = config.get('general',
                           'nproc',
                           mapper=int,
                           failfunc=process.preferred_process_count)
    except Exception as e:
        _throw('Invalid optional nproc', e, 'general')

    try:
        # Determine the sampling period and a global temporal offset
        dt = config.get(ssec, 'period', mapper=float)
        t0 = config.get(ssec, 'offset', mapper=float)
    except Exception as e:
        _throw('Configuration must specify period and offset', e, ssec)

    # Override the number of samples in WaveformMap
    try:
        kwargs['nsamp'] = config.get(ssec, 'nsamp', mapper=int)
    except HabisNoOptionError:
        pass
    except Exception as e:
        _throw('Invalid optional nsamp', e, ssec)

    # Determine the oversampling rate to use when cross-correlating
    try:
        osamp = config.get(ssec, 'osamp', mapper=int, default=1)
    except Exception as e:
        _throw('Invalid optional osamp', e, ssec)

    try:
        neighbors = config.get(asec, 'neighbors', default=None)
        if neighbors:
            kwargs['neighbors'] = loadkeymat(neighbors, dtype=int)
    except Exception as e:
        _throw('Invalid optional neighbors', e)

    # Determine the range of elements to use; default to all (as None)
    try:
        kwargs['minsnr'] = config.getlist(asec, 'minsnr', mapper=int)
    except HabisNoOptionError:
        pass
    except Exception as e:
        _throw('Invalid optional minsnr', e)

    # Determine a temporal window to apply before finding delays
    try:
        kwargs['window'] = config.get(asec, 'window')
    except HabisNoOptionError:
        pass
    except Exception as e:
        _throw('Invalid optional window', e)

    # Determine an energy leakage threshold
    try:
        kwargs['eleak'] = config.get(asec, 'eleak', mapper=float)
    except HabisNoOptionError:
        pass
    except Exception as e:
        _throw('Invalid optional eleak', e)

    # Determine a temporal window to apply before finding delays
    try:
        kwargs['bandpass'] = config.get(asec, 'bandpass')
    except HabisNoOptionError:
        pass
    except Exception as e:
        _throw('Invalid optional bandpass', e)

    # Determine peak-selection criteria
    try:
        kwargs['peaks'] = config.get(asec, 'peaks')
    except HabisNoOptionError:
        pass
    except Exception as e:
        _throw('Invalid optional peaks', e)

    # Determine IMER criteria
    try:
        kwargs['imer'] = config.get(asec, 'imer')
    except HabisNoOptionError:
        pass
    except Exception as e:
        _throw('Invalid optional imer', e)

    maskoutliers = config.get(asec, 'maskoutliers', mapper=bool, default=False)
    optimize = config.get(asec, 'optimize', mapper=bool, default=False)
    kwargs['negcorr'] = config.get(asec, 'negcorr', mapper=bool, default=False)
    kwargs['signsquare'] = config.get(asec,
                                      'signsquare',
                                      mapper=bool,
                                      default=False)
    kwargs['flipref'] = config.get(asec, 'flipref', mapper=bool, default=False)

    # Check for delay cache specifications as boolean or file suffix
    cachedelay = config.get(asec, 'cachedelay', default=True)
    if isinstance(cachedelay, bool) and cachedelay: cachedelay = 'delays.npz'

    try:
        # Remove the nearmap file key
        guesses = shsplit(kwargs['peaks'].pop('nearmap'))
        guesses = loadmatlist(guesses, nkeys=2, scalar=False)
    except IOError as e:
        guesses = None
        print(f'WARNING - Ignoring nearmap: {e}', file=sys.stderr)
    except (KeyError, TypeError, AttributeError):
        guesses = None
    else:
        # Adjust delay time scales
        guesses = {k: (v - t0) / dt for k, v in guesses.items()}

    # Adjust the delay time scales for the neardefault, if provided
    try:
        v = kwargs['peaks']['neardefault']
    except KeyError:
        pass
    else:
        kwargs['peaks']['neardefault'] = (v - t0) / dt

    try:
        # Load the window map, if provided
        winmap = shsplit(kwargs['window'].pop('map'))
        winmap = loadmatlist(winmap, nkeys=2, scalar=False)
    except IOError as e:
        winmap = None
        print(f'WARNING - Ignoring window map: {e}', file=sys.stderr)
    except (KeyError, TypeError, AttributeError):
        winmap = None
    else:
        # Replace the map argument with the loaded array
        kwargs['window']['map'] = winmap

    times = OrderedDict()

    # Process each target in turn
    for i, (target, datafiles) in enumerate(targetfiles.items()):
        if guesses:
            # Pull the column of the nearmap for this target
            nearmap = {k: v[i] for k, v in guesses.items()}
            kwargs['peaks']['nearmap'] = nearmap

        if cachedelay:
            delayfiles = buildpaths(datafiles, extension=cachedelay)
        else:
            delayfiles = [None] * len(datafiles)

        times[target] = dict()

        dltype = 'IMER' if kwargs.get('imer', None) else 'cross-correlation'
        ftext = 'files' if len(datafiles) != 1 else 'file'
        print(
            f'Finding {dltype} delays for {target} ({len(datafiles)} {ftext})')

        for (dfile, dlayfile) in zip(datafiles, delayfiles):
            kwargs['cachefile'] = dlayfile

            delays = finddelays(nproc, dfile, reffile, osamp, **kwargs)

            # Note the receive channels in this data file
            lrx = set(k[1] for k in delays.keys())

            # Convert delays to arrival times
            delays = {k: v * dt + t0 for k, v in delays.items()}

            if any(dv < 0 for dv in delays.values()):
                raise ValueError('Non-physical, negative delays exist')

            if maskoutliers:
                # Remove outlying values from the delay dictionary
                delays = stats.mask_outliers(delays)

            if optimize:
                # Prepare the arrival-time finder
                atf = trilateration.ArrivalTimeFinder(delays)
                # Compute the optimized times for this data file
                optimes = {(k, k): v for k, v in atf.lsmr() if k in lrx}
            else:
                # Just pass through the desired times
                optimes = delays

            times[target].update(optimes)

    # Build the combined times list
    for tmap in times.values():
        try:
            rxset.intersection_update(tmap.keys())
        except NameError:
            rxset = set(tmap.keys())

    if not len(rxset):
        raise ValueError(
            'Different targets have no common receive-channel indices')

    # Cast to Python float to avoid numpy dependencies in pickled output
    ctimes = {i: [float(t[i]) for t in times.values()] for i in sorted(rxset)}

    # Save the output as a pickled map
    savez_keymat(outfile, ctimes)
コード例 #4
0
ファイル: atimes.py プロジェクト: ahesford/habis-tools
def finddelays(nproc=1, *args, **kwargs):
    '''
	Distribute, among nproc processes, delay analysis for waveforms using
	calcdelays(). All *args and **kwargs, are passed to calcdelays on each
	participating process. This function explicitly sets the "queue",
	"rank", "grpsize", and "delaycache" arguments of calcdelays, so *args
	and **kwargs should not contain these values.

	The delaycache argument is built from an optional file specified in
	cachefile, which should be a map from transmit-receive pair (t, r) to a
	precomputed delay, loadable with habis.formats.loadkeymat.
	'''
    forbidden = {'queue', 'rank', 'grpsize', 'delaycache'}
    forbidden.intersection_update(kwargs)
    if forbidden:
        raise TypeError("Forbidden argument '{next(iter(forbidden))}'")

    cachefile = kwargs.pop('cachefile', None)

    # Try to read an existing delay map
    try:
        kwargs['delaycache'] = loadkeymat(cachefile)
    except (KeyError, ValueError, IOError):
        pass

    # Create a result queue and a dictionary to accumulate results
    queue = multiprocessing.Queue(nproc)
    delays = {}

    # Extend the kwargs to include the result queue
    kwargs['queue'] = queue
    # Extend the kwargs to include the group size
    kwargs['grpsize'] = nproc

    # Keep track of waveform statistics
    stats = defaultdict(int)

    # Spawn the desired processes to perform the cross-correlation
    with process.ProcessPool() as pool:
        for i in range(nproc):
            # Pick a useful process name
            procname = process.procname(i)
            # Add the group rank to the kwargs
            kwargs['rank'] = i
            # Extend kwargs to contain the queue (copies kwargs)
            pool.addtask(target=calcdelays,
                         name=procname,
                         args=args,
                         kwargs=kwargs)

        pool.start()

        # Wait for all processes to respond
        responses, deadpool = 0, False
        while responses < nproc:
            try:
                results = queue.get(timeout=0.1)
            except pyqueue.Empty:
                # Loosely join to watch for a dead pool
                pool.wait(timeout=0.1, limit=1)
                if not pool.unjoined:
                    # Note a dead pool, give read one more try
                    if deadpool: break
                    else: deadpool = True
            else:
                delays.update(results[0])
                for k, v in results[1].items():
                    if v: stats[k] += v
                responses += 1

        if responses != nproc:
            print(f'WARNING: Proceeding with {responses} of {nproc} '
                  'subprocess results. A subprocess may have died.')

        pool.wait()

    if stats:
        print(f'For file {os.path.basename(args[0])} '
              f'({len(delays)} identfied times):')
        for k, v in sorted(stats.items()):
            if v:
                wfn = 'waveforms' if v > 1 else 'waveform'
                print(f'  {v} {k} {wfn}')

    if len(delays) and cachefile:
        # Save the computed delays, if desired
        try:
            savez_keymat(cachefile, delays)
        except (ValueError, IOError):
            pass

    return delays
コード例 #5
0
        src, rcv = elements[t], targets[r]

        if not eikonal:
            # Use path tracing; only the path integral matters
            tracer.set_slowness(s)
            atimes[t, r] = tracer.trace(src, rcv, intonly=True)
        else:
            # Use the Eikonal solution
            if t != lt or tmi is None:
                # Compute interpolated solution for a new transmitter
                tmi = LinearInterpolator3D(eik.gauss(src, s))
                lt = t
            # Interpolate the arrival time at the receiver
            grcv = bx.cart2cell(*rcv)
            atimes[t, r] = tmi.evaluate(*grcv, grad=False)

        if not rank and i == ipow:
            ipow <<= 1
            print('Rank 0: Finished path %d of %d' % (i, share))

    # Make sure all participants have finished
    MPI.COMM_WORLD.Barrier()

    # Collect all arrival times
    atimes = MPI.COMM_WORLD.gather(atimes)

    if not rank:
        # Collapse individual arrival-time maps
        atimes = {(t, r): v for l in atimes for (t, r), v in l.items()}
        savez_keymat(output, atimes)
コード例 #6
0
ファイル: tomo.py プロジェクト: ahesford/habis-tools
    def lsmr(self,
             s,
             epochs=1,
             coleq=False,
             tmin=0.,
             chambolle=None,
             postfilter=None,
             partial_output=None,
             lsmropts={},
             omega=1.,
             bent_fallback=False,
             mindiff=False,
             save_pathmat=None,
             save_times=None):
        '''
		For each of epochs rounds, compute, using LSMR, a slowness
		image that satisfies the straight-ray arrival-time equations
		implicit in this CSRTomographyTask instance. The solution is
		represented as a perturbation to the slowness s, an instance of
		habis.slowness.Slowness or its descendants, defined on the grid
		self.tracer.box.

		If coleq is True, the columns of each path-length operator will
		be scaled so that they all have unity norm. The value of coleq
		can also be a float that specifies the minimum allowable column
		norm (as a fraction of the maximum norm) to avoid excessive
		scaling of very weak columns.

		The LSMR implementation uses pycwp.iterative.lsmr to support
		arrival times distributed across multiple MPI tasks in
		self.comm. The keyword arguments lsmropts will be passed to
		lsmr to customize the solution process. Forbidden keyword
		arguments are "A", "b", "unorm" and "vnorm".

		If lsmropts contains a 'maxiter' keyword, it can be a single
		integer or a list of integers. If the value is a list, it
		provides the maximum number of LSMR iterations for each epoch
		in sequence. If the total number of epochs exceeds the number
		of values in the list, the final value will be repeated as
		necessary.

		Within each epoch, an update to the slowness image is computed
		based on the difference between the compensated arrival time
		produced by self.comptimes(s, tmin, bent_fallback) and the
		actual arrival times in self.atimes.

		When mindiff is True, compensated arrival times will be replaced
		by straight-ray times whenever the straight-ray time is closer
		to the measured data for the path.

		The parameter omega should be a float used to damp updates at
		the end of each epoch. In other words, if ds is the update
		computed in an epoch, the slowness s at the end of the epoch
		will be updated according to

			s <- s + omega * ds.

		If chambolle is not None, it should be the "weight" parameter
		to the function skimage.restoration.denoise_tv_chambolle. In
		this case, the denoising filter will be applied to the slowness
		image after each epoch. Alternatively, chambolle can be a list
		of weights, in which case it behaves like the 'maxiter'
		argument of lsmropts.

		After each epoch, if partial_output is True, a partial solution
		will be saved by calling

		  self.save(partial_output, s, epoch, postfilter).

		The return value will be the final, perturbed solution. If
		postfilter is True, the solution s will be processed as
		postfilter(s) before it is returned.

		If save_pathmat is not None, it should be a string template
		which will be formatted with

		  pname = save_pathmat.format(rank=self.comm.rank)

		and used as a file name in which the reduced path-length matrix
		will be stored as a COO matrix in an NPZ file with keys data,
		row and col, corresponding to the attributes of the COO matrix.
		An additional key, 'trpairs', records the transmit-receive
		pairs that correspond to each row in the local path matrix.

		If save_times is not None, it should be a string template which
		will be formatted as

		  rname = save_times.format(epoch=epoch)

		After each epoch, an array of compensated and uncompensated
		straight-ray path integrals will be stored in a keymat file
		with name rname. All times will be coalesced onto the root rank
		for output.
		'''
        if not self.isRoot:
            # Make sure non-root tasks are always silent
            lsmropts['show'] = False

        ncell = self.tracer.box.ncell

        # Set a default for Boolean coleq
        if coleq is True: coleq = 1e-3
        elif coleq: coleq = float(coleq)

        # Composite slowness transform and path-length operator as CSR
        pathmat = (self.pathmat @ s.tosparse()).tocsr()

        if save_pathmat:
            # Save the path-length matrix
            pname = save_pathmat.format(rank=self.comm.rank)
            pcoo = pathmat.tocoo()
            np.savez(pname,
                     data=pcoo.data,
                     row=pcoo.row,
                     col=pcoo.col,
                     trpairs=self.trpairs)
            del pcoo, pname

        def unorm(u):
            # Synchronize
            self.comm.Barrier()
            # Norm of distributed vectors, to all ranks
            un = norm(u)**2
            return np.sqrt(self.comm.allreduce(un, op=MPI.SUM))

        # Process maxiter argument to allow per-epoch specification
        maxiter = lsmropts.pop('maxiter', None)
        try:
            maxiter = list(maxiter)
        except TypeError:
            itercounts = repeat(maxiter)
        else:
            itercounts = (maxiter[min(i, len(maxiter) - 1)] for i in count())

        try:
            chambolle = list(chambolle)
        except TypeError:
            chamwts = repeat(chambolle)
        else:
            chamwts = (chambolle[min(i, len(chambolle) - 1)] for i in count())

        msgfmt = ('Epoch {0} RMSE {1:0.6g} dsol {2:0.6g} '
                  'dct {3:0.6g} dst {4:0.6g} paths {5}')
        epoch, sol, ltimes = 0, 0, {}
        ns = s.perturb(sol)
        while True:
            # Adjust RHS times with straight-ray compensation
            tv = self.comptimes(ns, tmin, bent_fallback)
            # Find RMS arrival-time error for compensated model
            terr = fsum((v[0] - self.atimes[k])**2 for k, v in tv.items())
            tn = self.comm.allreduce(len(tv), op=MPI.SUM)
            terr = self.comm.allreduce(terr, op=MPI.SUM)
            terr = np.sqrt(terr / tn)

            # Compute norms of model times and time changes
            tdiffs = []
            for k in set(tv).intersection(ltimes):
                a, b = tv[k]
                c, d = ltimes[k]
                tdiffs.append([(a - c)**2, (b - d)**2, a**2, b**2])
            # Reduce square norms across all ranks
            if tdiffs: tdiffs = np.sum(tdiffs, axis=0)
            else: tdiffs = np.array([0.] * 4, dtype=np.float64)
            self.comm.Allreduce(MPI.IN_PLACE, tdiffs, op=MPI.SUM)
            # Set placeholder values if there is no value
            if tdiffs[2] == 0: tdiffs[0] = tdiffs[2] = 1.
            if tdiffs[3] == 0: tdiffs[1] = tdiffs[3] = 1.
            ltimes = tv

            if save_times:
                tv = self.comm.gather(tv)
                if self.isRoot:
                    rname = save_times.format(epoch=epoch)
                    tv = dict(kp for v in tv for kp in v.items())
                    savez_keymat(rname, tv)

            rkeys = []
            rhs = []
            for i, (t, r) in enumerate(self.trpairs):
                try:
                    tc, ts = ltimes[t, r]
                    ta = self.atimes[t, r]
                except KeyError:
                    continue

                if mindiff: tc = min([tc, ts], key=lambda x: abs(x - ta))

                rhs.append(ta - tc)
                rkeys.append(i)

            # Separate the RHS into row keys and values
            rhs = np.array(rhs)

            lpmat = pathmat[rkeys, :]

            if coleq:
                # Compute norms of columns of global matrix
                colscale = snorm(lpmat, axis=0)**2
                self.comm.Allreduce(MPI.IN_PLACE, colscale, op=MPI.SUM)
                np.sqrt(colscale, colscale)

                # Clip normalization factors to avoid blow-up
                mxnrm = np.max(colscale)
                np.clip(colscale, coleq * mxnrm, mxnrm, colscale)
            else:
                colscale = None

            # Include column scaling in the matrix-vector product
            def mvp(x):
                if colscale is not None: x = x / colscale
                v = lpmat @ x
                return v

            # Transpose operation requires communications
            def amvp(u):
                # Synchronize
                self.comm.Barrier()
                # Multiple by transposed local share, then flatten
                v = lpmat.T @ u
                if colscale is not None: v /= colscale
                # Accumulate contributions from all ranks
                self.comm.Allreduce(MPI.IN_PLACE, v, op=MPI.SUM)
                return v

            # Build the linear operator representing the path matrix
            A = LinearOperator(shape=lpmat.shape,
                               matvec=mvp,
                               rmatvec=amvp,
                               dtype=lpmat.dtype)

            # Use the right maxiter value for this epoch
            results = lsmr(A,
                           rhs,
                           unorm=unorm,
                           vnorm=norm,
                           maxiter=next(itercounts),
                           **lsmropts)

            ds = results[0]
            if colscale is not None: ds /= colscale

            cmwt = next(chamwts)
            if cmwt:
                ds = denoise_tv_chambolle(s.unflatten(ds), cmwt)
                ds = s.flatten(ds)

            if self.isRoot:
                # Compute relative change in solution
                dsnrm = norm(ds) / norm(sol + ds)

                ctnrm = np.sqrt(tdiffs[0] / tdiffs[2])
                stnrm = np.sqrt(tdiffs[1] / tdiffs[3])
                print(msgfmt.format(epoch, terr, dsnrm, ctnrm, stnrm, tn))

            # Update the solution
            sol = sol + omega * ds
            ns = s.perturb(sol)

            if partial_output:
                self.save(partial_output, ns, epoch, postfilter)

            epoch += 1
            if epoch > epochs: break

        if postfilter: ns = postfilter(ns)
        return ns