def _compute(self): # Invert arrays so the first element is the bottom of column T = _climlab_to_convect(self.state['Tatm']) dom = self.state['Tatm'].domain P = _climlab_to_convect(dom.lev.points) PH = _climlab_to_convect(dom.lev.bounds) Q = _climlab_to_convect(self.state['q']) QS = qsat(T, P) ND = np.size(T, axis=1) NCOL = np.size(T, axis=0) NL = ND - 1 try: U = _climlab_to_convect(self.state['U']) except: U = np.zeros_like(T) try: V = _climlab_to_convect(self.state['V']) except: V = np.zeros_like(T) NTRA = 1 TRA = np.zeros((NCOL, ND, NTRA), order='F') # tracers ignored DELT = float(self.timestep) CBMF = self.CBMF (IFLAG, FT, FQ, FU, FV, FTRA, PRECIP, WD, TPRIME, QPRIME, CBMFnew, Tout, Qout, QSout, Uout, Vout, TRAout) = \ convect(T, Q, QS, U, V, TRA, P, PH, NCOL, ND, NL, NTRA, DELT, self.IPBL, CBMF, CPD, CPV, CL, RV, RD, LV0, G, ROWL, self.MINORIG, self.ELCRIT, self.TLCRIT, self.ENTP, self.SIGD, self.SIGS, self.OMTRAIN, self.OMTSNOW, self.COEFFR, self.COEFFS, self.CU, self.BETA, self.DTMAX, self.ALPHA, self.DAMP ) # If dry adjustment is being used then the tendencies need to be adjusted if self.IPBL != 0: FT += (Tout - T) / DELT FQ += (Qout - Q) / DELT tendencies = { 'Tatm': _convect_to_climlab(FT) * np.ones_like(self.state['Tatm']), 'q': _convect_to_climlab(FQ) * np.ones_like(self.state['q']) } if 'Ts' in self.state: # for some strange reason self.Ts is breaking tests under Python 3.5 in some configurations tendencies['Ts'] = 0. * self.state['Ts'] if 'U' in self.state: tendencies['U'] = _convect_to_climlab(FU) * np.ones_like( self.state['U']) if 'V' in self.state: tendencies['V'] = _convect_to_climlab(FV) * np.ones_like( self.state['V']) self.CBMF = CBMFnew # Need to convert from mm/day to mm/s or kg/m2/s # Hack to handle single column and multicolumn if self.multidim: self.precipitation[:, 0] = _convect_to_climlab( PRECIP) / const.seconds_per_day else: self.precipitation[:] = _convect_to_climlab( PRECIP) / const.seconds_per_day self.IFLAG = IFLAG self.relative_humidity[:] = self.q / qsat(self.Tatm, self.lev) return tendencies
def moist_amplification_factor(Tkelvin, relative_humidity=0.8): '''Compute the moisture amplification factor for the moist diffusivity given relative humidity and reference temperature profile.''' deltaT = 0.01 # slope of saturation specific humidity at 1000 hPa dqsdTs = (qsat(Tkelvin + deltaT / 2, 1000.) - qsat(Tkelvin - deltaT / 2, 1000.)) / deltaT return const.Lhvap / const.cp * relative_humidity * dqsdTs
def test_thermo_domain(): '''Can we call qsat, etc on a multi-dim climlab state temperature object?''' state = climlab.column_state(num_lev = 30, num_lat=3) T = state.Tatm p = T.domain.lev.points thermo.clausius_clapeyron(T) thermo.qsat(T, p) thermo.pseudoadiabat(T, p) thermo.blackbody_emission(T)
def test_thermo_domain(): '''Can we call qsat, etc on a multi-dim climlab state temperature object?''' state = climlab.column_state(num_lev = 30, num_lat=3) T = state.Tatm p = T.domain.lev.points thermo.clausius_clapeyron(T) thermo.qsat(T, p) thermo.pseudoadiabat(T, p) thermo.blackbody_emission(T)
def _compute(self): # Invert arrays so the first element is the bottom of column T = _climlab_to_convect(self.state['Tatm']) dom = self.state['Tatm'].domain P = _climlab_to_convect(dom.lev.points) PH = _climlab_to_convect(dom.lev.bounds) Q = _climlab_to_convect(self.state['q']) QS = qsat(T,P) ND = np.size(T, axis=1) NCOL = np.size(T, axis=0) NL = ND-1 try: U = _climlab_to_convect(self.state['U']) except: U = np.zeros_like(T) try: V = _climlab_to_convect(self.state['V']) except: V = np.zeros_like(T) NTRA = 1 TRA = np.zeros((NCOL,ND,NTRA), order='F') # tracers ignored DELT = float(self.timestep) CBMF = self.CBMF (IFLAG, FT, FQ, FU, FV, FTRA, PRECIP, WD, TPRIME, QPRIME, CBMFnew, Tout, Qout, QSout, Uout, Vout, TRAout) = \ convect(T, Q, QS, U, V, TRA, P, PH, NCOL, ND, NL, NTRA, DELT, self.IPBL, CBMF, CPD, CPV, CL, RV, RD, LV0, G, ROWL, self.MINORIG, self.ELCRIT, self.TLCRIT, self.ENTP, self.SIGD, self.SIGS, self.OMTRAIN, self.OMTSNOW, self.COEFFR, self.COEFFS, self.CU, self.BETA, self.DTMAX, self.ALPHA, self.DAMP ) # If dry adjustment is being used then the tendencies need to be adjusted if self.IPBL != 0: FT += (Tout - T) / DELT FQ += (Qout - Q) / DELT tendencies = {'Tatm': _convect_to_climlab(FT)*np.ones_like(self.state['Tatm']), 'q': _convect_to_climlab(FQ)*np.ones_like(self.state['q'])} if 'Ts' in self.state: # for some strange reason self.Ts is breaking tests under Python 3.5 in some configurations tendencies['Ts'] = 0. * self.state['Ts'] if 'U' in self.state: tendencies['U'] = _convect_to_climlab(FU) * np.ones_like(self.state['U']) if 'V' in self.state: tendencies['V'] = _convect_to_climlab(FV) * np.ones_like(self.state['V']) self.CBMF = CBMFnew # Need to convert from mm/day to mm/s or kg/m2/s # Hack to handle single column and multicolumn if self.multidim: self.precipitation[:,0] = _convect_to_climlab(PRECIP)/const.seconds_per_day else: self.precipitation[:] = _convect_to_climlab(PRECIP)/const.seconds_per_day self.IFLAG = IFLAG self.relative_humidity[:] = self.q / qsat(self.Tatm,self.lev) return tendencies
def _compute_flux(self): # specific humidity at lowest model level # assumes pressure is the last axis q = Field(self.q[..., -1, np.newaxis], domain=self.Ts.domain) Ta = Field(self.Tatm[..., -1, np.newaxis], domain=self.Ts.domain) qs = qsat(self.Ts, self.ps) Deltaq = Field(qs - q, domain=self.Ts.domain) rho = self._air_density(Ta) # flux from bulk formula self._flux = const.Lhvap * rho * self.Cd * self.U * Deltaq self.LHF = self._flux
def _compute_flux(self): # specific humidity at lowest model level # assumes pressure is the last axis q = self.q[..., -1, np.newaxis] Ta = self.Tatm[..., -1, np.newaxis] qs = qsat(self.Ts, self.ps) Deltaq = qs - q rho = self._air_density(Ta) # flux from bulk formula self._flux = const.Lhvap * rho * self.Cd * self.U * Deltaq self.LHF = self._flux
def compute_flux(self): # specific humidity at lowest model level # assumes pressure is the last axis q = self.q[..., 0, np.newaxis] Ta = self.Tatm[..., 0, np.newaxis] qs = qsat(self.Ts, const.ps) Deltaq = qs - q # air density rho = const.ps * const.mb_to_Pa / const.Rd / Ta # flux from bulk formula self.flux = const.Lhvap * rho * self.Cd * self.U * Deltaq self.diagnostics['LHF'] = self.flux
def compute_flux(self): # specific humidity at lowest model level # assumes pressure is the last axis q = self.q[..., 0, np.newaxis] Ta = self.Tatm[..., 0, np.newaxis] qs = qsat(self.Ts, const.ps) Deltaq = qs - q # air density rho = const.ps * const.mb_to_Pa / const.Rd / Ta # flux from bulk formula self.flux = const.Lhvap * rho * self.Cd * self.U * Deltaq self.diagnostics['LHF'] = self.flux
def _compute_flux(self): # specific humidity at lowest model level # assumes pressure is the last axis q = Field(self.q[..., -1, np.newaxis], domain=self.Ts.domain) Ta = Field(self.Tatm[..., -1, np.newaxis], domain=self.Ts.domain) qs = qsat(self.Ts, self.ps) Deltaq = Field(qs - q, domain=self.Ts.domain) rho = self._air_density(Ta) # flux from bulk formula self._flux = self.resistance * const.Lhvap * rho * self.Cd * self.U * Deltaq self.LHF[:] = self._flux # evporation rate, convert from W/m2 to kg/m2/s (or mm/s) self.evaporation[:] = self.LHF / const.Lhvap
def _compute_flux(self): # specific humidity at lowest model level # assumes pressure is the last axis q = Field(self.q[..., -1, np.newaxis], domain=self.Ts.domain) Ta = Field(self.Tatm[..., -1, np.newaxis], domain=self.Ts.domain) qs = qsat(self.Ts, self.ps) Deltaq = Field(qs - q, domain=self.Ts.domain) rho = self._air_density(Ta) # flux from bulk formula self._flux = self.resistance * const.Lhvap * rho * self.Cd * self.U * Deltaq self.LHF[:] = self._flux # evporation rate, convert from W/m2 to kg/m2/s (or mm/s) self.evaporation[:] = self.LHF/const.Lhvap
def test_thermo(): '''Basic single value tests for the thermodynamic routines.''' assert np.isclose(thermo.potential_temperature(250., 500.), 304.783) assert np.isclose(thermo.theta(250., 500.), 304.783) assert np.isclose(thermo.temperature_from_potential(300., 500.), 246.076) assert np.isclose(thermo.T(300., 500.), 246.076) assert np.isclose(thermo.clausius_clapeyron(300.), 35.345) assert np.isclose(thermo.qsat(300., 1000.), 0.02227839) assert np.isclose(thermo.estimated_inversion_strength(300., 290.), 5.3605345) assert np.isclose(thermo.EIS(300., 290.), 5.3605345) assert np.isclose(thermo.blackbody_emission(300.), 459.3)
def test_thermo(): '''Basic single value tests for the thermodynamic routines.''' assert np.isclose(thermo.potential_temperature(250., 500.), 304.783) assert np.isclose(thermo.theta(250., 500.), 304.783) assert np.isclose(thermo.temperature_from_potential(300., 500.), 246.076) assert np.isclose(thermo.T(300., 500.), 246.076) assert np.isclose(thermo.clausius_clapeyron(300.), 35.345) assert np.isclose(thermo.qsat(300., 1000.), 0.02227839) assert np.isclose(thermo.estimated_inversion_strength(300., 290.), 2.58025) assert np.isclose(thermo.EIS(300., 290.), 2.58025) assert np.isclose(thermo.blackbody_emission(300.), 459.3)
def toa_flux(ctrl, pert, kernels, cloud_mask_correction=0.): '''Compute changes in TOA flux using radiative kernels. Inputs are xray dataset handles for model climatology files, and another dataset for the kernels. The sign convention is that the computed flux is POSITIVE DOWN (source of energy) thus the flux has the same sign as the corresponding feedback once normalized by surface temperature change. Currently implemented for CAM4 aquaplanet output.''' # The kernels are not on the same grid as the CAM4 output lev_kernel = kernels.pfull dp_kernel = np.tile(np.transpose(np.atleast_2d(np.diff(kernels.phalf.data))),(1,90)) # Flux is calculated by convolving the kernel with # temperature / humidity response in the TROPOSPHERE # define a mask from surface to 100 mb at equator, # decreasing linearly to 300 mb at the poles # (following Feldl and Roe 2013) maxlev = np.tile(100. + (200. * np.abs(kernels.lat)/90.), (lev_kernel.size, 1)) p_kernel = np.tile(np.expand_dims(lev_kernel, axis=1), (1, kernels.lat.size)) dp_masked = np.ma.masked_array(dp_kernel, np.where(p_kernel > maxlev, False, True)) # this custom function will compute vertical integrals # taking advantage of xray named coordinates def integral(field): return (field * dp_masked).sum(dim='pfull') # Temperature anomalies at every point in the latitude - pressure plane DeltaT = pert.data_vars['T'] - ctrl.data_vars['T'] # Surface temperature anomalies DeltaTS = pert.data_vars['TS'] - ctrl.data_vars['TS'] # 2D interpolation for the tropospheric temperature anomalies # here I think I need to abandon the xray dataset and just use numpy field = regrid(ctrl.lat.data, ctrl.lev.data, DeltaT.data, kernels.lat.data, lev_kernel.data) DeltaT_interp = np.ma.masked_array(field, np.isnan(field)) # These use the "masked array" module (part of numpy) # to make it easy to mask out missing data # 1D interpolation for the surface temperature anomalies field = np.interp(kernels.lat, DeltaTS.lat, DeltaTS) DeltaTS_interp = np.ma.masked_array(field, np.isnan(field)) # finite difference approximation to the slope d/dT (q_saturation) small = 0.01 dqsatdT = (qsat(ctrl.data_vars['T']+small, ctrl.lev) - qsat(ctrl.data_vars['T']-small, ctrl.lev)) / (2*small) # actual specific humidity anomalies DeltaQ = pert.Q - ctrl.Q # relative humidity in control run (convert from percent to fraction) RH_ctrl = ctrl.RELHUM / 100. # Equivalent temperature change # (actual humidity change expressed as temperature change at fixed RH) DeltaTequiv = DeltaQ / (RH_ctrl * dqsatdT ) # Scaled by local surface temp. anomaly #DeltaTequiv_scaled = DeltaTequiv / DeltaTS # But actually we are supposed to be using log(q) DeltaLogQ = np.log(pert.Q) - np.log(ctrl.Q) dlogqsatdT = (np.log(qsat(ctrl.data_vars['T']+small, ctrl.lev)) - np.log(qsat(ctrl.data_vars['T']-small, ctrl.lev))) / (2*small) DeltaTequiv_log = DeltaLogQ / (dlogqsatdT) # Interpolated to kernel grid: field = regrid(ctrl.lat.data, ctrl.lev.data, DeltaTequiv_log.data, kernels.lat.data, lev_kernel.data) DeltaTequiv_interp = np.ma.masked_array(field, np.isnan(field)) # create a new dictionary to hold all the feedbacks flux = {} # Compute the feedbacks! flux['lw_q'] = integral(kernels['lw_q'] * DeltaTequiv_interp) # shortwave water vapor flux['sw_q'] = integral(kernels['sw_q'] * DeltaTequiv_interp) # longwave temperature (Planck and lapse rate) flux['Planck'] = (kernels['lw_ts'] + integral(kernels['lw_t'])) * DeltaTS_interp flux['lapse'] = integral(kernels['lw_t'] * (DeltaT_interp-DeltaTS_interp)) flux['lw_t'] = flux['Planck'] + flux['lapse'] # Add up the longwave feedbacks flux['lw_net'] = flux['lw_t'] + flux['lw_q'] # Compute cloud radiative forcing # first, the all-sky TOA radiation fields #try: # DeltaOLR = pert.FLNT - ctrl.FLNT # DeltaASR = pert.FSNT - ctrl.FSNT # # and the clear-sky diagnostics # DeltaOLRclr = pert.FLNTC - ctrl.FLNTC # DeltaASRclr = pert.FSNTC - ctrl.FSNTC # # Cloud radiative forcing is just the difference between these: #except: DeltaOLR = pert.FLNT.data - ctrl.FLNT.data DeltaASR = pert.FSNT.data - ctrl.FSNT.data # and the clear-sky diagnostics DeltaOLRclr = pert.FLNTC.data - ctrl.FLNTC.data DeltaASRclr = pert.FSNTC.data - ctrl.FSNTC.data # Cloud radiative forcing is just the difference between these: DeltaOLRcld = DeltaOLR - DeltaOLRclr DeltaASRcld = DeltaASR - DeltaASRclr # Soden et al. 2008 show how to compute the cloud feedback accurately # accounting for cloud masking -- see their equation (25) # The cloud radiative forcing itself, interpolated flux['CRF_sw'] = np.interp( kernels.lat, ctrl.lat, DeltaASRcld ) flux['CRF_lw'] = np.interp( kernels.lat, ctrl.lat, -DeltaOLRcld ) flux['CRF_net'] = flux['CRF_sw'] + flux['CRF_lw'] # The corrections... # water vapor flux['cld_q_lw'] = integral(kernels['cld_q_lw'] * DeltaTequiv_interp) flux['cld_q_sw'] = integral(kernels['cld_q_sw'] * DeltaTequiv_interp) flux['cld_q'] = flux['cld_q_lw'] + flux['cld_q_sw'] # temperature flux['cld_t'] = ( kernels['cld_ts']*DeltaTS_interp + integral(kernels['cld_t'] * DeltaT_interp) ) # Corrected cloud feedbacks flux['cloud_lw'] = flux['CRF_lw'] + flux['cld_t'] + flux['cld_q_lw'] flux['cloud_sw'] = flux['CRF_sw'] + flux['cld_q_sw'] flux['cloud'] = flux['cloud_lw'] + flux['cloud_sw'] # Soden et al. (2008) argue that there should be an additional # correction due to cloud masking of radiative forcing... # This is only an issue for 2xCO2 forcing. # Soden et al. suggest 0.69 W/m2/K globally flux['cloud'] += cloud_mask_correction # Total net feedback flux['total'] = flux['cloud'] + flux['lw_net'] + flux['sw_q'] # Alternate description using fixed relative humidity as state # variable following Held and Shell J. Clim. 2012 # temperature kernel for fixed relative humidity is the sum of # traditional temperature and traditional water vapor kernels kernels['lw_talt'] = kernels['lw_t'] + kernels['lw_q'] flux['Planck_alt'] = (kernels['lw_ts'] + integral(kernels['lw_talt'])) * DeltaTS_interp flux['lapse_alt'] = integral(kernels['lw_talt'] * (DeltaT_interp-DeltaTS_interp)) # Get RH feedback by subtracting original water vapor kernel times # atmospheric temperature response from traditional water vapor feedback. flux['RH'] = flux['lw_q'] - integral(kernels['lw_q'] * DeltaT_interp) # package output into xray datasets flux_dataset = xray.Dataset(variables=flux) return flux_dataset
def toa_flux(ctrl, pert, kernels, cloud_mask_correction=0.): '''Compute changes in TOA flux using radiative kernels. Inputs are xarray dataset handles for model climatology files, and another dataset for the kernels. The sign convention is that the computed flux is POSITIVE DOWN (source of energy) thus the flux has the same sign as the corresponding feedback once normalized by surface temperature change. Currently implemented for CAM4 aquaplanet output.''' # The kernels are not on the same grid as the CAM4 output lev_kernel = kernels.pfull dp_kernel = np.tile( np.transpose(np.atleast_2d(np.diff(kernels.phalf.data))), (1, 90)) # Flux is calculated by convolving the kernel with # temperature / humidity response in the TROPOSPHERE # define a mask from surface to 100 mb at equator, # decreasing linearly to 300 mb at the poles # (following Feldl and Roe 2013) maxlev = np.tile(100. + (200. * np.abs(kernels.lat) / 90.), (lev_kernel.size, 1)) p_kernel = np.tile(np.expand_dims(lev_kernel, axis=1), (1, kernels.lat.size)) dp_masked = np.ma.masked_array(dp_kernel, np.where(p_kernel > maxlev, False, True)) # this custom function will compute vertical integrals # taking advantage of xarray named coordinates def integral(field): return (field * dp_masked).sum(dim='pfull') # Temperature anomalies at every point in the latitude - pressure plane DeltaT = pert.data_vars['TA'] - ctrl.data_vars['TA'] # Surface temperature anomalies DeltaTS = pert.data_vars['TS'] - ctrl.data_vars['TS'] # 2D interpolation for the tropospheric temperature anomalies # here I think I need to abandon the xarray dataset and just use numpy field = regrid(ctrl.lat.data, ctrl.lev.data, DeltaT.data, kernels.lat.data, lev_kernel.data) DeltaT_interp = np.ma.masked_array(field, np.isnan(field)) # These use the "masked array" module (part of numpy) # to make it easy to mask out missing data # 1D interpolation for the surface temperature anomalies field = np.interp(kernels.lat, DeltaTS.lat, DeltaTS) DeltaTS_interp = np.ma.masked_array(field, np.isnan(field)) # finite difference approximation to the slope d/dT (q_saturation) small = 0.01 dqsatdT = (qsat(ctrl.data_vars['TA'] + small, ctrl.lev) - qsat(ctrl.data_vars['TA'] - small, ctrl.lev)) / (2 * small) # actual specific humidity anomalies DeltaQ = pert.Q - ctrl.Q # relative humidity in control run (convert from percent to fraction) RH_ctrl = ctrl.RELHUM / 100. # Equivalent temperature change # (actual humidity change expressed as temperature change at fixed RH) DeltaTequiv = DeltaQ / (RH_ctrl * dqsatdT) # Scaled by local surface temp. anomaly #DeltaTequiv_scaled = DeltaTequiv / DeltaTS # But actually we are supposed to be using log(q) DeltaLogQ = np.log(pert.Q) - np.log(ctrl.Q) dlogqsatdT = (np.log(qsat(ctrl.data_vars['TA'] + small, ctrl.lev)) - np.log(qsat(ctrl.data_vars['TA'] - small, ctrl.lev))) / ( 2 * small) DeltaTequiv_log = DeltaLogQ / (dlogqsatdT) # Interpolated to kernel grid: field = regrid(ctrl.lat.data, ctrl.lev.data, DeltaTequiv_log.data, kernels.lat.data, lev_kernel.data) DeltaTequiv_interp = np.ma.masked_array(field, np.isnan(field)) # create a new dictionary to hold all the feedbacks flux = {} # Compute the feedbacks! flux['lw_q'] = integral(kernels['lw_q'] * DeltaTequiv_interp) # shortwave water vapor flux['sw_q'] = integral(kernels['sw_q'] * DeltaTequiv_interp) # longwave temperature (Planck and lapse rate) flux['Planck'] = (kernels['lw_ts'] + integral(kernels['lw_t'])) * DeltaTS_interp flux['lapse'] = integral(kernels['lw_t'] * (DeltaT_interp - DeltaTS_interp)) flux['lw_t'] = flux['Planck'] + flux['lapse'] # Add up the longwave feedbacks flux['lw_net'] = flux['lw_t'] + flux['lw_q'] # Compute cloud radiative forcing # first, the all-sky TOA radiation fields DeltaOLR = pert.FLNT - ctrl.FLNT DeltaASR = pert.FSNT - ctrl.FSNT # and the clear-sky diagnostics DeltaOLRclr = pert.FLNTC - ctrl.FLNTC DeltaASRclr = pert.FSNTC - ctrl.FSNTC # Cloud radiative forcing is just the difference between these: DeltaOLRcld = DeltaOLR - DeltaOLRclr DeltaASRcld = DeltaASR - DeltaASRclr # Soden et al. 2008 show how to compute the cloud feedback accurately # accounting for cloud masking -- see their equation (25) # The cloud radiative forcing itself, interpolated flux['CRF_sw'] = np.interp(kernels.lat, ctrl.lat, DeltaASRcld) flux['CRF_lw'] = np.interp(kernels.lat, ctrl.lat, -DeltaOLRcld) flux['CRF_net'] = flux['CRF_sw'] + flux['CRF_lw'] # The corrections... # water vapor flux['cld_q_lw'] = integral(kernels['cld_q_lw'] * DeltaTequiv_interp) flux['cld_q_sw'] = integral(kernels['cld_q_sw'] * DeltaTequiv_interp) flux['cld_q'] = flux['cld_q_lw'] + flux['cld_q_sw'] # temperature flux['cld_t'] = (kernels['cld_ts'] * DeltaTS_interp + integral(kernels['cld_t'] * DeltaT_interp)) # Corrected cloud feedbacks flux['cloud_lw'] = flux['CRF_lw'] + flux['cld_t'] + flux['cld_q_lw'] flux['cloud_sw'] = flux['CRF_sw'] + flux['cld_q_sw'] flux['cloud'] = flux['cloud_lw'] + flux['cloud_sw'] # Soden et al. (2008) argue that there should be an additional # correction due to cloud masking of radiative forcing... # This is only an issue for 2xCO2 forcing. # Soden et al. suggest 0.69 W/m2/K globally flux['cloud'] += cloud_mask_correction # Total net feedback flux['total'] = flux['cloud'] + flux['lw_net'] + flux['sw_q'] # Alternate description using fixed relative humidity as state # variable following Held and Shell J. Clim. 2012 # temperature kernel for fixed relative humidity is the sum of # traditional temperature and traditional water vapor kernels kernels['lw_talt'] = kernels['lw_t'] + kernels['lw_q'] flux['Planck_alt'] = (kernels['lw_ts'] + integral(kernels['lw_talt'])) * DeltaTS_interp flux['lapse_alt'] = integral(kernels['lw_talt'] * (DeltaT_interp - DeltaTS_interp)) # Get RH feedback by subtracting original water vapor kernel times # atmospheric temperature response from traditional water vapor feedback. flux['RH'] = flux['lw_q'] - integral(kernels['lw_q'] * DeltaT_interp) # package output into xarray datasets flux_dataset = xr.Dataset(data_vars=flux) return flux_dataset