def linear_forward_model_rho(self, shot, m0, m1, return_parameters=[], dWaveOp0=None, wavefield=None): """Applies the forward model to the model for the given solver in terms of a pertubation of rho. Parameters ---------- shot : pysit.Shot Gives the source signal approximation for the right hand side. m0 : solver.ModelParameters The parameters upon where to center the linear approximation. m1 : solver.ModelParameters The parameters upon which to apply the linear forward model to. return_parameters : list of {'wavefield1', 'dWaveOp1', 'dWaveOp0', 'simdata'} Values to return. u0tt : ndarray Derivative field required for the imaging condition to be used as right hand side. Returns ------- retval : dict Dictionary whose keys are return_parameters that contains the specified data. Notes ----- * u1 is used as the target field universally. It could be velocity potential, it could be displacement, it could be pressure. * u1tt is used to generically refer to the derivative of u1 that is needed to compute the imaging condition. * If u0tt is not specified, it may be computed on the fly at potentially high expense. """ # Local references solver = self.solver solver.model_parameters = m0 # this updates dt and the number of steps so that is appropriate for the current model mesh = solver.mesh sh=mesh.shape(include_bc=True,as_grid=True) d = solver.domain dt = solver.dt nsteps = solver.nsteps source = shot.sources model_2=1.0/m1.rho model_2=mesh.pad_array(model_2) #Lap = build_heterogenous_(sh,model_2,[mesh.x.delta,mesh.z.delta]) rp=dict() rp['laplacian']=True Lap = build_heterogenous_matrices(sh,[mesh.x.delta,mesh.z.delta],model_2.reshape(-1,),rp=rp) # Storage for the field if 'wavefield1' in return_parameters: us = list() # Setup data storage for the forward modeled data if 'simdata' in return_parameters: simdata = np.zeros((solver.nsteps, shot.receivers.receiver_count)) # Storage for the time derivatives of p if 'dWaveOp0' in return_parameters: dWaveOp0ret = list() if 'dWaveOp1' in return_parameters: dWaveOp1 = list() # Step k = 0 # p_0 is a zero array because if we assume the input signal is causal # and we assume that the initial system (i.e., p_(-2) and p_(-1)) is # uniformly zero, then the leapfrog scheme would compute that p_0 = 0 as # well. ukm1 is needed to compute the temporal derivative. solver_data = solver.SolverData() if dWaveOp0 is None: solver_data_u0 = solver.SolverData() # For u0, set up the right hand sides rhs_u0_k = np.zeros(mesh.shape(include_bc=True)) rhs_u0_kp1 = np.zeros(mesh.shape(include_bc=True)) rhs_u0_k = self._setup_forward_rhs(rhs_u0_k, source.f(0*dt)) rhs_u0_kp1 = self._setup_forward_rhs(rhs_u0_kp1, source.f(1*dt)) # compute u0_kp1 so that we can compute dWaveOp0_k (needed for u1) solver.time_step(solver_data_u0, rhs_u0_k, rhs_u0_kp1) # compute dwaveop_0 (k=0) and allocate space for kp1 (needed for u1 time step) dWaveOp0_k = solver.compute_dWaveOp('time', solver_data_u0) dWaveOp0_kp1 = dWaveOp0_k.copy() solver_data_u0.advance() # from here, it makes more sense to refer to rhs_u0 as kp1 and kp2, because those are the values we need # to compute u0_kp2, which is what we need to compute dWaveOp0_kp1 rhs_u0_kp1, rhs_u0_kp2 = rhs_u0_k, rhs_u0_kp1 # to reuse the allocated space and setup the swap that occurs a few lines down else: solver_data_u0 = None for k in xrange(nsteps): u0k=wavefield[k] if k < (nsteps-1): u0kp1=wavefield[k+1] else: u0kp1=wavefield[k] u0k=mesh.pad_array(u0k) u0kp1=mesh.pad_array(u0kp1) uk = solver_data.k.primary_wavefield uk_bulk = mesh.unpad_array(uk) if 'wavefield1' in return_parameters: us.append(uk_bulk.copy()) # Record the data at t_k if 'simdata' in return_parameters: shot.receivers.sample_data_from_array(uk_bulk, k, data=simdata) # Note, we compute result for k+1 even when k == nsteps-1. We need # it for the time derivative at k=nsteps-1. if dWaveOp0 is None: # compute u0_kp2 so we can get dWaveOp0_kp1 for the rhs for u1 rhs_u0_kp1, rhs_u0_kp2 = rhs_u0_kp2, rhs_u0_kp1 rhs_u0_kp2 = self._setup_forward_rhs(rhs_u0_kp2, source.f((k+2)*dt)) solver.time_step(solver_data_u0, rhs_u0_kp1, rhs_u0_kp2) # shift the dWaveOp0's (ok at k=0 because they are equal then) dWaveOp0_k, dWaveOp0_kp1 = dWaveOp0_kp1, dWaveOp0_k dWaveOp0_kp1 = solver.compute_dWaveOp('time', solver_data_u0) solver_data_u0.advance() else: dWaveOp0_k = dWaveOp0[k] dWaveOp0_kp1 = dWaveOp0[k+1] if k < (nsteps-1) else dWaveOp0[k] # incase not enough dWaveOp0's are provided, repeat the last one if 'dWaveOp0' in return_parameters: dWaveOp0ret.append(dWaveOp0_k) G0=Lap*u0k G1=Lap*u0kp1 if k == 0: rhs_k = G0 rhs_kp1 = G1 else: rhs_k, rhs_kp1 = rhs_kp1, G1 solver.time_step(solver_data, rhs_k, rhs_kp1) # Compute time derivative of p at time k if 'dWaveOp1' in return_parameters: dWaveOp1.append(solver.compute_dWaveOp('time', solver_data)) # When k is the nth step, the next step is uneeded, so don't swap # any values. This way, uk at the end is always the final step if(k == (nsteps-1)): break # Don't know what data is needed for the solver, so the solver data # handles advancing everything forward by one time step. # k-1 <-- k, k <-- k+1, etc solver_data.advance() retval = dict() if 'wavefield1' in return_parameters: retval['wavefield1'] = us if 'dWaveOp0' in return_parameters: retval['dWaveOp0'] = dWaveOp0ret if 'dWaveOp1' in return_parameters: retval['dWaveOp1'] = dWaveOp1 if 'simdata' in return_parameters: retval['simdata'] = simdata return retval
def adjoint_model(self, shot, m0, operand_simdata, imaging_period, operand_dWaveOpAdj=None, operand_model=None, return_parameters=[], dWaveOp=None, wavefield=None): """Solves for the adjoint field. For constant density: m*q_tt - lap q = resid, where m = 1.0/c**2 For variable density: m1*q_tt - div(m2 grad)q = resid, where m1=1.0/kappa, m2=1.0/rho, and C = (kappa/rho)**0.5 Parameters ---------- shot : pysit.Shot Gives the receiver model for the right hand side. operand_simdata : ndarray Right hand side component in the data space, usually the residual. operand_dWaveOpAdj : list of ndarray Right hand side component in the wave equation space, usually something to do with the imaging component...this needs resolving operand_simdata : ndarray Right hand side component in the data space, usually the residual. return_parameters : list of {'adjointfield', 'ic'} dWaveOp : ndarray Imaging component from the forward model. Returns ------- retval : dict Dictionary whose keys are return_parameters that contains the specified data. Notes ----- * q is the adjoint field. * ic is the imaging component. Because this function computes many of the things required to compute the imaging condition, there is an option to compute the imaging condition as we go. This should be used to save computational effort. If the imaging condition is to be computed, the optional argument utt must be present. Imaging Condition for variable density has components: ic.m1 = u_tt * q ic.m2 = grad(u) dot grad(q) """ # Local references solver = self.solver solver.model_parameters = m0 mesh = solver.mesh d = solver.domain dt = solver.dt nsteps = solver.nsteps source = shot.sources if 'adjointfield' in return_parameters: qs = list() vs = list() # Storage for the time derivatives of p if 'dWaveOpAdj' in return_parameters: dWaveOpAdj = list() # If we are computing the imaging condition, ensure that all of the parts are there and allocate space. if dWaveOp is not None: ic = solver.model_parameters.perturbation() do_ic = True elif 'imaging_condition' in return_parameters: raise ValueError('To compute imaging condition, forward component must be specified.') else: do_ic = False # Variable-Density will call this, giving us matrices needed for the ic in terms of m2 (or rho) if hasattr(m0, 'kappa') and hasattr(m0,'rho'): deltas = [mesh.x.delta,mesh.z.delta] sh = mesh.shape(include_bc=True,as_grid=True) D1,D2=build_heterogenous_matrices(sh,deltas) # Time-reversed wave solver solver_data = solver.SolverData() rhs_k = np.zeros(mesh.shape(include_bc=True)) rhs_km1 = np.zeros(mesh.shape(include_bc=True)) if operand_model is not None: operand_model = operand_model.with_padding() # Loop goes over the valid indices backwards for k in xrange(nsteps-1, -1, -1): #xrange(int(solver.nsteps)): # Local reference vk = solver_data.k.primary_wavefield vk_bulk = mesh.unpad_array(vk) # If we are dealing with variable density, we will need the wavefield to compute the gradient of the objective in terms of m2. if hasattr(m0, 'kappa') and hasattr(m0,'rho'): uk = mesh.pad_array(wavefield[k]) # When dpdt is not set, store the current q, otherwise compute the # relevant gradient portion if 'adjointfield' in return_parameters: vs.append(vk_bulk.copy()) # can maybe speed up by using only the bulk and not unpadding later if do_ic: if k%imaging_period == 0: #Save every 'imaging_period' number of steps entry = k/imaging_period # if we are dealing with variable density, we compute 2 parts to the imagaing condition seperatly. Otherwise, if it is just constant density- we compute only 1. if hasattr(m0, 'kappa') and hasattr(m0,'rho'): ic.kappa += vk*dWaveOp[entry] ic.rho += (D1[0]*uk)*(D1[1]*vk)+(D2[0]*uk)*(D2[1]*vk) else: ic += vk*dWaveOp[entry] if k == nsteps-1: rhs_k = self._setup_adjoint_rhs( rhs_k, shot, k, operand_simdata, operand_model, operand_dWaveOpAdj) rhs_km1 = self._setup_adjoint_rhs( rhs_km1, shot, k-1, operand_simdata, operand_model, operand_dWaveOpAdj) else: # shift time forward rhs_k, rhs_km1 = rhs_km1, rhs_k rhs_km1 = self._setup_adjoint_rhs( rhs_km1, shot, k-1, operand_simdata, operand_model, operand_dWaveOpAdj) solver.time_step(solver_data, rhs_k, rhs_km1) # Compute time derivative of p at time k if 'dWaveOpAdj' in return_parameters: if k%imaging_period == 0: #Save every 'imaging_period' number of steps dWaveOpAdj.append(solver.compute_dWaveOp('time', solver_data)) # If k is 0, we don't need results for k-1, so save computation and # stop early if(k == 0): break # Don't know what data is needed for the solver, so the solver data # handles advancing everything forward by one time step. # k-1 <-- k, k <-- k+1, etc solver_data.advance() if do_ic: ic *= (-1*dt) ic *= imaging_period #Compensate for doing fewer summations at higher imaging_period ic = ic.without_padding() # gradient is never padded retval = dict() if 'adjointfield' in return_parameters: # List of qs is built in time reversed order, put them in time forward order qs = list(vs) qs.reverse() retval['adjointfield'] = qs if 'dWaveOpAdj' in return_parameters: dWaveOpAdj.reverse() retval['dWaveOpAdj'] = dWaveOpAdj if do_ic: retval['imaging_condition'] = ic return retval
def linear_forward_model_rho(self, shot, m0, m1, return_parameters=[], dWaveOp0=None, wavefield=None): """Applies the forward model to the model for the given solver in terms of a pertubation of rho. Parameters ---------- shot : pysit.Shot Gives the source signal approximation for the right hand side. m0 : solver.ModelParameters The parameters upon where to center the linear approximation. m1 : solver.ModelParameters The parameters upon which to apply the linear forward model to. return_parameters : list of {'wavefield1', 'dWaveOp1', 'dWaveOp0', 'simdata'} Values to return. u0tt : ndarray Derivative field required for the imaging condition to be used as right hand side. Returns ------- retval : dict Dictionary whose keys are return_parameters that contains the specified data. Notes ----- * u1 is used as the target field universally. It could be velocity potential, it could be displacement, it could be pressure. * u1tt is used to generically refer to the derivative of u1 that is needed to compute the imaging condition. * If u0tt is not specified, it may be computed on the fly at potentially high expense. """ # Local references solver = self.solver solver.model_parameters = m0 # this updates dt and the number of steps so that is appropriate for the current model mesh = solver.mesh sh=mesh.shape(include_bc=True,as_grid=True) d = solver.domain dt = solver.dt nsteps = solver.nsteps source = shot.sources model_2=1.0/m1.rho model_2=mesh.pad_array(model_2) #Lap = build_heterogenous_(sh,model_2,[mesh.x.delta,mesh.z.delta]) rp=dict() rp['laplacian']=True Lap = build_heterogenous_matrices(sh,[mesh.x.delta,mesh.z.delta],model_2.reshape(-1,),rp=rp) # Storage for the field if 'wavefield1' in return_parameters: us = list() # Setup data storage for the forward modeled data if 'simdata' in return_parameters: simdata = np.zeros((solver.nsteps, shot.receivers.receiver_count)) # Storage for the time derivatives of p if 'dWaveOp0' in return_parameters: dWaveOp0ret = list() if 'dWaveOp1' in return_parameters: dWaveOp1 = list() # Step k = 0 # p_0 is a zero array because if we assume the input signal is causal # and we assume that the initial system (i.e., p_(-2) and p_(-1)) is # uniformly zero, then the leapfrog scheme would compute that p_0 = 0 as # well. ukm1 is needed to compute the temporal derivative. solver_data = solver.SolverData() if dWaveOp0 is None: solver_data_u0 = solver.SolverData() # For u0, set up the right hand sides rhs_u0_k = np.zeros(mesh.shape(include_bc=True)) rhs_u0_kp1 = np.zeros(mesh.shape(include_bc=True)) rhs_u0_k = self._setup_forward_rhs(rhs_u0_k, source.f(0*dt)) rhs_u0_kp1 = self._setup_forward_rhs(rhs_u0_kp1, source.f(1*dt)) # compute u0_kp1 so that we can compute dWaveOp0_k (needed for u1) solver.time_step(solver_data_u0, rhs_u0_k, rhs_u0_kp1) # compute dwaveop_0 (k=0) and allocate space for kp1 (needed for u1 time step) dWaveOp0_k = solver.compute_dWaveOp('time', solver_data_u0) dWaveOp0_kp1 = dWaveOp0_k.copy() solver_data_u0.advance() # from here, it makes more sense to refer to rhs_u0 as kp1 and kp2, because those are the values we need # to compute u0_kp2, which is what we need to compute dWaveOp0_kp1 rhs_u0_kp1, rhs_u0_kp2 = rhs_u0_k, rhs_u0_kp1 # to reuse the allocated space and setup the swap that occurs a few lines down else: solver_data_u0 = None for k in xrange(nsteps): u0k=wavefield[k] if k < (nsteps-1): u0kp1=wavefield[k+1] else: u0kp1=wavefield[k] u0k=mesh.pad_array(u0k) u0kp1=mesh.pad_array(u0kp1) uk = solver_data.k.primary_wavefield uk_bulk = mesh.unpad_array(uk) if 'wavefield1' in return_parameters: us.append(uk_bulk.copy()) # Record the data at t_k if 'simdata' in return_parameters: shot.receivers.sample_data_from_array(uk_bulk, k, data=simdata) # Note, we compute result for k+1 even when k == nsteps-1. We need # it for the time derivative at k=nsteps-1. if dWaveOp0 is None: # compute u0_kp2 so we can get dWaveOp0_kp1 for the rhs for u1 rhs_u0_kp1, rhs_u0_kp2 = rhs_u0_kp2, rhs_u0_kp1 rhs_u0_kp2 = self._setup_forward_rhs(rhs_u0_kp2, source.f((k+2)*dt)) solver.time_step(solver_data_u0, rhs_u0_kp1, rhs_u0_kp2) # shift the dWaveOp0's (ok at k=0 because they are equal then) dWaveOp0_k, dWaveOp0_kp1 = dWaveOp0_kp1, dWaveOp0_k dWaveOp0_kp1 = solver.compute_dWaveOp('time', solver_data_u0) solver_data_u0.advance() else: dWaveOp0_k = dWaveOp0[k] dWaveOp0_kp1 = dWaveOp0[k+1] if k < (nsteps-1) else dWaveOp0[k] # incase not enough dWaveOp0's are provided, repeat the last one if 'dWaveOp0' in return_parameters: dWaveOp0ret.append(dWaveOp0_k) G0=Lap*u0k G1=Lap*u0kp1 if k == 0: rhs_k = G0 rhs_kp1 = G1 else: rhs_k, rhs_kp1 = rhs_kp1, G1 solver.time_step(solver_data, rhs_k, rhs_kp1) # Compute time derivative of p at time k if 'dWaveOp1' in return_parameters: dWaveOp1.append(solver.compute_dWaveOp('time', solver_data)) # When k is the nth step, the next step is uneeded, so don't swap # any values. This way, uk at the end is always the final step if(k == (nsteps-1)): break # Don't know what data is needed for the solver, so the solver data # handles advancing everything forward by one time step. # k-1 <-- k, k <-- k+1, etc solver_data.advance() retval = dict() if 'wavefield1' in return_parameters: retval['wavefield1'] = us if 'dWaveOp0' in return_parameters: retval['dWaveOp0'] = dWaveOp0ret if 'dWaveOp1' in return_parameters: retval['dWaveOp1'] = dWaveOp1 if 'simdata' in return_parameters: retval['simdata'] = simdata return retval
def adjoint_model(self, shot, m0, operand_simdata, imaging_period, operand_dWaveOpAdj=None, operand_model=None, return_parameters=[], dWaveOp=None, wavefield=None): """Solves for the adjoint field. For constant density: m*q_tt - lap q = resid, where m = 1.0/c**2 For variable density: m1*q_tt - div(m2 grad)q = resid, where m1=1.0/kappa, m2=1.0/rho, and C = (kappa/rho)**0.5 Parameters ---------- shot : pysit.Shot Gives the receiver model for the right hand side. operand_simdata : ndarray Right hand side component in the data space, usually the residual. operand_dWaveOpAdj : list of ndarray Right hand side component in the wave equation space, usually something to do with the imaging component...this needs resolving operand_simdata : ndarray Right hand side component in the data space, usually the residual. return_parameters : list of {'adjointfield', 'ic'} dWaveOp : ndarray Imaging component from the forward model. Returns ------- retval : dict Dictionary whose keys are return_parameters that contains the specified data. Notes ----- * q is the adjoint field. * ic is the imaging component. Because this function computes many of the things required to compute the imaging condition, there is an option to compute the imaging condition as we go. This should be used to save computational effort. If the imaging condition is to be computed, the optional argument utt must be present. Imaging Condition for variable density has components: ic.m1 = u_tt * q ic.m2 = grad(u) dot grad(q) """ # Local references solver = self.solver solver.model_parameters = m0 mesh = solver.mesh d = solver.domain dt = solver.dt nsteps = solver.nsteps source = shot.sources if 'adjointfield' in return_parameters: qs = list() vs = list() # Storage for the time derivatives of p if 'dWaveOpAdj' in return_parameters: dWaveOpAdj = list() # If we are computing the imaging condition, ensure that all of the parts are there and allocate space. if dWaveOp is not None: ic = solver.model_parameters.perturbation() do_ic = True elif 'imaging_condition' in return_parameters: raise ValueError('To compute imaging condition, forward component must be specified.') else: do_ic = False # Variable-Density will call this, giving us matrices needed for the ic in terms of m2 (or rho) if hasattr(m0, 'kappa') and hasattr(m0,'rho'): deltas = [mesh.x.delta,mesh.z.delta] sh = mesh.shape(include_bc=True,as_grid=True) D1,D2=build_heterogenous_matrices(sh,deltas) # Time-reversed wave solver solver_data = solver.SolverData() rhs_k = np.zeros(mesh.shape(include_bc=True)) rhs_km1 = np.zeros(mesh.shape(include_bc=True)) if operand_model is not None: operand_model = operand_model.with_padding() # Loop goes over the valid indices backwards for k in xrange(nsteps-1, -1, -1): #xrange(int(solver.nsteps)): # Local reference vk = solver_data.k.primary_wavefield vk_bulk = mesh.unpad_array(vk) # If we are dealing with variable density, we will need the wavefield to compute the gradient of the objective in terms of m2. if hasattr(m0, 'kappa') and hasattr(m0,'rho'): uk = mesh.pad_array(wavefield[k]) # When dpdt is not set, store the current q, otherwise compute the # relevant gradient portion if 'adjointfield' in return_parameters: vs.append(vk_bulk.copy()) # can maybe speed up by using only the bulk and not unpadding later if do_ic: if k%imaging_period == 0: #Save every 'imaging_period' number of steps entry = k/imaging_period # if we are dealing with variable density, we compute 2 parts to the imagaing condition seperatly. Otherwise, if it is just constant density- we compute only 1. if hasattr(m0, 'kappa') and hasattr(m0,'rho'): ic.kappa += vk*dWaveOp[entry] ic.rho += (D1[0]*uk)*(D1[1]*vk)+(D2[0]*uk)*(D2[1]*vk) else: ic += vk*dWaveOp[entry] if k == nsteps-1: rhs_k = self._setup_adjoint_rhs( rhs_k, shot, k, operand_simdata, operand_model, operand_dWaveOpAdj) rhs_km1 = self._setup_adjoint_rhs( rhs_km1, shot, k-1, operand_simdata, operand_model, operand_dWaveOpAdj) else: # shift time forward rhs_k, rhs_km1 = rhs_km1, rhs_k rhs_km1 = self._setup_adjoint_rhs( rhs_km1, shot, k-1, operand_simdata, operand_model, operand_dWaveOpAdj) solver.time_step(solver_data, rhs_k, rhs_km1) # Compute time derivative of p at time k if 'dWaveOpAdj' in return_parameters: if k%imaging_period == 0: #Save every 'imaging_period' number of steps dWaveOpAdj.append(solver.compute_dWaveOp('time', solver_data)) # If k is 0, we don't need results for k-1, so save computation and # stop early if(k == 0): break # Don't know what data is needed for the solver, so the solver data # handles advancing everything forward by one time step. # k-1 <-- k, k <-- k+1, etc solver_data.advance() if do_ic: ic *= (-1*dt) ic *= imaging_period #Compensate for doing fewer summations at higher imaging_period ic = ic.without_padding() # gradient is never padded retval = dict() if 'adjointfield' in return_parameters: # List of qs is built in time reversed order, put them in time forward order qs = list(vs) qs.reverse() retval['adjointfield'] = qs if 'dWaveOpAdj' in return_parameters: dWaveOpAdj.reverse() retval['dWaveOpAdj'] = dWaveOpAdj if do_ic: retval['imaging_condition'] = ic return retval
def linear_forward_model_rho(self, shot, m0, m1, frequencies, return_parameters=[], dWaveOp0=None, wavefield=None): """Applies the forward model to the model for the given solver in terms of a pertubation of rho Parameters ---------- shot : pysit.Shot Gives the source signal approximation for the right hand side. m1 : solver.ModelParameters frequencies : list of 2-tuples 2-tuple, first element is the frequency to use, second element the weight. return_parameters : list of {'dWaveOp0', 'wavefield1', 'dWaveOp1', 'simdata', 'simdata_time'}, optional Values to return. Returns ------- retval : dict Dictionary whose keys are return_parameters that contains the specified data. Notes ----- * u1 is used as the target field universally. It could be velocity potential, it could be displacement, it could be pressure. * u1tt is used to generically refer to the derivative of u1 that is needed to compute the imaging condition. * If u0tt is not specified, it may be computed on the fly at potentially high expense. """ # Sanitize the input if not np.iterable(frequencies): frequencies = [frequencies] # Local references solver = self.solver solver.model_parameters = m0 # this updates dt and the number of steps so that is appropriate for the current model mesh = solver.mesh sh = mesh.shape(include_bc=True,as_grid=True) d = solver.domain source = shot.sources model_2=1.0/m1.rho model_2=mesh.pad_array(model_2) rp=dict() rp['laplacian']=True Lap = build_heterogenous_matrices(sh,[mesh.x.delta,mesh.z.delta],model_2.reshape(-1,),rp=rp) # Storage for the field u1hats = dict() # Setup data storage for the forward modeled data if 'simdata' in return_parameters: simdata = dict() # Storage for the time derivatives of p if 'dWaveOp0' in return_parameters: dWaveOp0ret = dict() # Storage for the time derivatives of p if 'dWaveOp1' in return_parameters: dWaveOp1 = dict() if dWaveOp0 is None: solver_data_u0 = solver.SolverData() solver_data = solver.SolverData() rhs = solver.WavefieldVector(mesh,dtype=solver.dtype) for nu in frequencies: u0_hat=wavefield[nu] u0_hat=mesh.pad_array(u0_hat) if dWaveOp0 is None: rhs = solver.build_rhs(mesh.pad_array(source.f(nu=nu)), rhs_wavefieldvector=rhs) solver.solve(solver_data_u0, rhs, nu) u0hat = solver_data_u0.k.primary_wavefield dWaveOp0_nu = solver.compute_dWaveOp('frequency', u0hat, nu) else: dWaveOp0_nu = dWaveOp0[nu] if 'dWaveOp0' in return_parameters: dWaveOp0ret[nu] = dWaveOp0_nu rhs_ = Lap*u0_hat rhs = solver.build_rhs(rhs_, rhs_wavefieldvector=rhs) # make the rhs vector the correct length solver.solve(solver_data,rhs,nu) u1hat = solver_data.k.primary_wavefield # Store the wavefield if 'wavefield1' in return_parameters: u1hats[nu] = mesh.unpad_array(u1hat, copy=True) # Compute the derivative if 'dWaveOp1' in return_parameters: dWaveOp1[nu] = solver.compute_dWaveOp('frequency', u1hat, nu) # Extract the data if 'simdata' in return_parameters: simdata[nu] = shot.receivers.sample_data_from_array(mesh.unpad_array(u1hat)) retval = dict() if 'dWaveOp0' in return_parameters: retval['dWaveOp0'] = dWaveOp0ret if 'wavefield1' in return_parameters: retval['wavefield1'] = u1hats if 'dWaveOp1' in return_parameters: retval['dWaveOp1'] = dWaveOp1 if 'simdata' in return_parameters: retval['simdata'] = simdata return retval
def adjoint_model(self, shot, m0, operand_simdata, frequencies, operand_dWaveOpAdj=None, operand_model=None, frequency_weights=None, return_parameters=[], dWaveOp=None, wavefield=None): """Solves for the adjoint field in frequency. For constant density: -m*(omega**2)*q - lap q = resid, where m = 1.0/c**2 For variable density: -m1*(omega**2)*q - div(m2 grad)q = resid, where m1=1.0/kappa, m2=1.0/rho, and C = (kappa/rho)**0.5 Parameters ---------- shot : pysit.Shot Gives the receiver model for the right hand side. operand : ndarray Right hand side, usually the residual. frequencies : list of 2-tuples 2-tuple, first element is the frequency to use, second element the weight. return_parameters : list of {'q', 'qhat', 'ic'} dWaveOp : ndarray Imaging component from the forward model (in frequency). Returns ------- retval : dict Dictionary whose keys are return_parameters that contains the specified data. Notes ----- * q is the adjoint field. * qhat is the DFT of oq at the specified frequencies * ic is the imaging component. Because this function computes many of the things required to compute the imaging condition, there is an option to compute the imaging condition as we go. This should be used to save computational effort. If the imaging condition is to be computed, the optional argument utt must be present. Imaging Condition for variable density has terms: ic.m1 = omegas**2 * conj(u) * q ic.m2 = conj(grad(u)) dot grad(q), summed over all shots and frequencies. """ # Sanitize the input if not np.iterable(frequencies): frequencies = [frequencies] # Local references solver = self.solver solver.model_parameters = m0 mesh = solver.mesh d = solver.domain source = shot.sources # Sanitize the input if not np.iterable(frequencies): frequencies = [frequencies] qhats = dict() if 'dWaveOpAdj' in return_parameters: dWaveOpAdj = dict() # If we are computing the imaging condition, ensure that all of the parts are there. if dWaveOp is None and 'imaging_condition' in return_parameters: raise ValueError('To compute imaging condition, forward component must be specified.') if 'imaging_condition' in return_parameters: ic = solver.model_parameters.perturbation(dtype=np.complex) if frequency_weights is None: frequency_weights = itertools.repeat(1.0) freq_weights = {nu: weight for nu,weight in zip(frequencies,frequency_weights)} # if we are dealing with variable density, we need to collect the gradient operators, D1 and D2. (note: D2 is the negative adjoint of the leftmost gradient used in our heterogenous laplacian) if hasattr(m0, 'kappa') and hasattr(m0,'rho'): deltas = [mesh.x.delta,mesh.z.delta] sh = mesh.shape(include_bc=True,as_grid=True) D1, D2 = build_heterogenous_matrices(sh,deltas) if operand_model is not None: operand_model = operand_model.with_padding() # Time-reversed wave solver solver_data = solver.SolverData() rhs = solver.WavefieldVector(mesh,dtype=solver.dtype) for nu in frequencies: # If we are dealing with variable density, we will need these values computed for the imagining condition in terms of m2. if hasattr(m0, 'kappa') and hasattr(m0,'rho'): uhat = wavefield[nu] uhat = mesh.pad_array(uhat) D1u, D2u = np.conj(D1[0]*uhat), np.conj(D2[0]*uhat) # Need the conj. of grad (uhat) # Compute the rhs array. rhs_ = mesh.pad_array(shot.receivers.extend_data_to_array(data=operand_simdata[nu])) # for primary adjoint equation if (operand_dWaveOpAdj is not None) and (operand_model is not None): dWaveOpAdj_nu = operand_dWaveOpAdj[nu] rhs_ += reshape(operand_model*dWaveOpAdj_nu.reshape(operand_model.shape), rhs_.shape) # for secondary adjoint equation rhs = solver.build_rhs(rhs_, rhs_wavefieldvector=rhs) np.conj(rhs.data, rhs.data) result = solver.solve(solver_data, rhs, nu) vhat = solver_data.k.primary_wavefield # Compute the conjugate in place. # After this operation, vhats _is_ conjugated, so its value does not # match the mathematics. This is done to save computation, as computing # the conjufation in place requires no further allocation. vhats should # not be used beyond this point, so it is assigned to None. qhat = np.conj(vhat, vhat) if 'adjointfield' in return_parameters: qhats[nu] = mesh.unpad_array(qhat, copy=True) if 'dWaveOpAdj' in return_parameters: dWaveOpAdj[nu] = solver.compute_dWaveOp('frequency', qhat,nu) # If the imaging component needs to be computed, do it if 'imaging_condition' in return_parameters: weight = freq_weights[nu] # if we are dealing with variable density, we compute 2 parts to the imagaing condition seperatly. Otherwise, if it is just constant density- we compute only 1. if hasattr(m0, 'kappa') and hasattr(m0, 'rho'): ic.rho -= weight*((D1u)*(D1[1]*qhat)+(D2u)*(D2[1]*qhat)) ic.kappa -= weight*qhat*np.conj(dWaveOp[nu]) else: ic -= weight*qhat*np.conj(dWaveOp[nu]) # note, no dnu here because the nus are not generally the complete set, so dnu makes little sense, otherwise dnu = 1./(nsteps*dt) retval = dict() if 'adjointfield' in return_parameters: retval['adjointfield'] = qhats if 'dWaveOpAdj' in return_parameters: retval['dWaveOpAdj'] = dWaveOpAdj # If the imaging component needs to be computed, do it if 'imaging_condition' in return_parameters: retval['imaging_condition'] = ic.without_padding() return retval