def test_xarray_ufuncs_deprecation(): with pytest.warns(FutureWarning, match="xarray.ufuncs"): xu.cos(xr.DataArray([0, 1])) with pytest.warns(None) as record: xu.angle(xr.DataArray([0, 1])) assert len(record) == 0
def _horizontal_metrics_from_geographical_coordinates(latitudes, longitudes): """Return horizontal scale factors computed from lat, lon arrays. Parameters ---------- latitudes : xarray dataarray array of latitudes from which to build the grid metrics. assume that the order of the dimensions is ('y','x'). longitudes :xarray dataarray array of latitudes from which to build the grid metrics. assume that the order of the dimensions is ('y','x'). Return ------ e1 : xarray dataarray Array of grid cell width corresponding to cell_x_size_at_*_location e2 : xarray dataarray Array of grid cell width corresponding to cell_y_size_at_*_location """ #- Define the centered first order derivatives of lat/lon arrays dlat_dj, dlat_di = _horizontal_gradient(latitudes) dlon_dj, dlon_di = _horizontal_gradient(longitudes) #- Define the approximate size of the cells in x and y direction e1 = earthrad * deg2rad \ * sqrt(( dlon_di * cos( deg2rad * latitudes ) )**2. + dlat_di**2.) e2 = earthrad * deg2rad \ * sqrt(( dlon_dj * cos( deg2rad * latitudes ) )**2. + dlat_dj**2.) return e1, e2
def lon_lat_to_cartesian(lon, lat, radius=1): """ calculates lon, lat coordinates of a point on a sphere with radius radius """ # Unpack xarray object into plane arrays if hasattr(lon, 'data'): lon = lon.data if hasattr(lat, 'data'): lat = lat.data if lon.ndim != lat.ndim: raise ValueError('coordinate must share the same number of dimensions') if lon.ndim == 1: lon, lat = np.meshgrid(lon, lat) lon_r = xu.radians(lon) lat_r = xu.radians(lat) x = radius * xu.cos(lat_r) * xu.cos(lon_r) y = radius * xu.cos(lat_r) * xu.sin(lon_r) z = radius * xu.sin(lat_r) return x.flatten(), y.flatten(), z.flatten()
def angle2xyz(azi, zen): """Convert azimuth and zenith to cartesian.""" azi = xu.deg2rad(azi) zen = xu.deg2rad(zen) x = xu.sin(zen) * xu.sin(azi) y = xu.sin(zen) * xu.cos(azi) z = xu.cos(zen) return x, y, z
def lonlat2xyz(lon, lat): """Convert lon lat to cartesian.""" lat = xu.deg2rad(lat) lon = xu.deg2rad(lon) x = xu.cos(lat) * xu.cos(lon) y = xu.cos(lat) * xu.sin(lon) z = xu.sin(lat) return x, y, z
def test_xarray_ufuncs_deprecation(): with pytest.warns(PendingDeprecationWarning, match='xarray.ufuncs'): xu.cos(xr.DataArray([0, 1])) with pytest.warns(None) as record: xu.angle(xr.DataArray([0, 1])) record = [el.message for el in record if el.category == PendingDeprecationWarning] assert len(record) == 0
def distance_to_point(self, lat, lon): """ Use Haversine formula to estimate distances from all gridpoints to a given location (lat, lon) """ R = 6371. # Radius of earth in km lat = np.radians(lat) lon = np.radians(lon) dlat = lat - xu.radians(self['lat'].values) dlon = lon - xu.radians(self['lon'].values) a = xu.sin(dlat/2)**2 + xu.cos(lat) * xu.cos(xu.radians(self['lat'].values)) * \ xu.sin(dlon/2)**2 c = 2 * xu.arctan2(xu.sqrt(a), xu.sqrt(1.0-a)) return R*c
def distance_to_point(self, lat, lon): """ Use Haversine formula to estimate distances from all gridpoints to a given location (lat, lon) """ R = 6371. # Radius of earth in km lat = np.radians(lat) lon = np.radians(lon) dlat = lat - xu.radians(self['lat'].values) dlon = lon - xu.radians(self['lon'].values) a = xu.sin(dlat/2)**2 + xu.cos(lat) * xu.cos(xu.radians(self['lat'].values)) * \ xu.sin(dlon/2)**2 c = 2 * xu.arctan2(xu.sqrt(a), xu.sqrt(1.0 - a)) return R * c
def test_horizontal_divergence(self): # TODO : in 3D test divergence of curl is zero. tols = {'rtol': 1e-3, 'atol': 1e-3} s = self.scale divvar = self.grd.horizontal_divergence(self.vector) dxdy = grids._dj(self.xv).shift(y=1) \ / self.grd._arrays["cell_y_size_at_t_location"] # custom derivative actual_div = divvar.to_masked_array() expected_div = (xu.cos(self.xt * s) * s - xu.sin(self.yt * s) * s + xu.cos(self.xt * s) * s * dxdy).to_masked_array() #print_array_around(expected=expected_div/s,actual=actual_div/s) self.assertArray2dCloseInside(actual_div / s, expected_div / s, depth=2, **tols)
def test_horizontal_divergence(self): # TODO : in 3D test divergence of curl is zero. tols = {'rtol':1e-3,'atol':1e-3} s = self.scale divvar = self.grd.horizontal_divergence(self.vector) dxdy = grids._dj(self.xv).shift(y=1) \ / self.grd._arrays["cell_y_size_at_t_location"] # custom derivative actual_div = divvar.to_masked_array() expected_div = (xu.cos(self.xt * s) * s - xu.sin(self.yt * s) * s + xu.cos(self.xt * s) * s * dxdy ).to_masked_array() #print_array_around(expected=expected_div/s,actual=actual_div/s) self.assertArray2dCloseInside(actual_div/s,expected_div/s, depth=2,**tols)
def test_horizontal_gradient(self): tols = {'rtol': 1e-3, 'atol': 1e-3} gradvar = self.grd.horizontal_gradient(self.tvar1) gradx = self.grd.horizontal_gradient(self.xt) #dxdy = gradx.y_component s = self.scale actual_gx = gradvar.x_component.to_masked_array() actual_gy = gradvar.y_component.to_masked_array() expected_gx = (xu.cos(self.xu * s) * s).to_masked_array() expected_gy = ( -xu.sin(self.yv * s) * s # + xu.cos(self.xv * s) * s * dxdy ).to_masked_array() self.assertArray2dCloseInside(actual_gx / s, expected_gx / s, depth=2, **tols) #print dxdy[:,20].values #print self.grd._arrays['cell_y_size_at_t_location'][:,20].values #print self.grd._arrays['cell_y_size_at_v_location'][:,20].values #print_array_around(expected=expected_gy/s,actual=actual_gy/s) self.assertArray2dCloseInside(actual_gy / s, expected_gy / s, depth=2, **tols)
def rotate_vector(u, v, angle): '''Rotate the vector (u, v) to (u_lon, v_lat). ** Input ** u: velocity in x direction (shape: nt,nj,ni) v: velocity in y direction (shape: nt,nj,ni) angle: the angle between x and longitude (in radians, shape: nj,ni) ** Returns ** u_lon: velocity in zonal direction (shape: nt,nj,ni) v_lat: velocity in meridional direction (shape: nt,nj,ni) ''' u_lon = u * cos(angle) - v * sin(angle) v_lat = u * sin(angle) + v * cos(angle) return u_lon, v_lat
def nearest_points(self, lat, lon, npt=1): """ Use the lat-lon arrays to return a list of indices of the nearest npt points to the given lat-lon """ # Use sin of lat lon to handle periodic # and not worry about if we are in negative # degrees dist = xu.hypot(xu.sin(xu.radians(self['lat'].values)) - xu.sin(xu.radians(lat)),\ xu.cos(xu.radians(self['lon'].values)) - xu.cos(xu.radians(lon))) # Get indices of the flattened array nearest_raw = dist.argsort(axis=None)[:npt] # Convert back to 2-d coords nearest = np.unravel_index(nearest_raw, self['lat'].shape) return nearest
def overturning_improved(run): # Better method: use the actual layer pressure intervals to weight the integral field = (run.V * run.dP * cos(deg2rad(run.lat))) if 'lon' in field.dims: field = field.mean(dim='lon') factor = 2*np.pi*physconst.rearth/physconst.gravit*1E-9 psi = np.cumsum( field, axis=field.get_axis_num('lev'))*factor return psi
def test_unary(self): args = [0, np.zeros(2), xr.Variable(['x'], [0, 0]), xr.DataArray([0, 0], dims='x'), xr.Dataset({'y': ('x', [0, 0])})] for a in args: self.assertIdentical(a + 1, xu.cos(a))
def setUp(self): self.x = np.arange(start=0, stop=101, step=10, dtype=float) self.y = np.arange(start=0, stop=101, step=10, dtype=float) self.xx, self.yy = np.meshgrid(self.x, self.y) self.xrx = xr.DataArray(self.xx, dims=['y', 'x']) self.xry = xr.DataArray(self.yy, dims=['y', 'x']) self.xrt = xu.sin(self.xrx / 30.) + xu.cos(self.xry / 30.) self.t = np.sin(self.xx / 30.) + np.cos(self.yy / 30.)
def test_unary(self): args = [0, np.zeros(2), xr.Variable(['x'], [0, 0]), xr.DataArray([0, 0], dims='x'), xr.Dataset({'y': ('x', [0, 0])})] for a in args: self.assert_identical(a + 1, xu.cos(a))
def setUp(self): self.x = np.arange(start=0, stop=101, step=10,dtype=float) self.y = np.arange(start=0, stop=101, step=10,dtype=float) self.xx,self.yy = np.meshgrid(self.x, self.y) self.xrx = xr.DataArray(self.xx,dims=['y','x']) self.xry = xr.DataArray(self.yy,dims=['y','x']) self.xrt = xu.sin(self.xrx/30.) + xu.cos(self.xry/30.) self.t = np.sin(self.xx/30.) + np.cos(self.yy/30.)
def test_horizontal_laplacian(self): tols = {'rtol':1e-3,'atol':1e-3} l = self.grd.horizontal_laplacian(self.tvar2) s = self.scale print s actual_lap = l.to_masked_array() expected_lap = (-1. * xu.cos(self.yt * s) * s * s).to_masked_array() #print_array_around(expected=expected_lap/s**2,actual=actual_lap/s**2) self.assertArray2dCloseInside(actual_lap/s**2,expected_lap/s**2, depth=4,**tols)
def __call__(self, projectables, **kwargs): projectables = self.check_areas(projectables) day_data = projectables[0] night_data = projectables[1] lim_low = np.cos(np.deg2rad(self.lim_low)) lim_high = np.cos(np.deg2rad(self.lim_high)) try: coszen = xu.cos(xu.deg2rad(projectables[2])) except IndexError: from pyorbital.astronomy import cos_zen LOG.debug("Computing sun zenith angles.") # Get chunking that matches the data try: chunks = day_data.sel(bands=day_data['bands'][0]).chunks except KeyError: chunks = day_data.chunks lons, lats = day_data.attrs["area"].get_lonlats_dask(chunks) coszen = xr.DataArray(cos_zen(day_data.attrs["start_time"], lons, lats), dims=['y', 'x'], coords=[day_data['y'], day_data['x']]) # Calculate blending weights coszen -= np.min((lim_high, lim_low)) coszen /= np.abs(lim_low - lim_high) coszen = coszen.clip(0, 1) # Apply enhancements to get images day_data = enhance2dataset(day_data) night_data = enhance2dataset(night_data) # Adjust bands so that they match # L/RGB -> RGB/RGB # LA/RGB -> RGBA/RGBA # RGB/RGBA -> RGBA/RGBA day_data = add_bands(day_data, night_data['bands']) night_data = add_bands(night_data, day_data['bands']) # Replace missing channel data with zeros day_data = zero_missing_data(day_data, night_data) night_data = zero_missing_data(night_data, day_data) # Get merged metadata attrs = combine_metadata(day_data, night_data) # Blend the two images together data = (1 - coszen) * night_data + coszen * day_data data.attrs = attrs # Split to separate bands so the mode is correct data = [data.sel(bands=b) for b in data['bands']] return super(DayNightCompositor, self).__call__(data, **kwargs)
def setUp(self): # only testing on a regular grid x = np.arange(start=0, stop=1.e7, step=1.e5,dtype=float) y = np.arange(start=0, stop=1.2e7, step=1.e5,dtype=float) x,y = np.meshgrid(x,y) self.grd = agrids.plane_2d_grid(ycoord=y,xcoord=x) self.scale = 2. * 3.14159 / 1.e7 self.xt = self.grd._arrays['plane_x_coordinate_at_t_location'] self.yt = self.grd._arrays['plane_y_coordinate_at_t_location'] self.xu = self.grd.change_grid_location_t_to_u(self.xt) self.yu = self.grd.change_grid_location_t_to_u(self.yt) self.xv = self.grd.change_grid_location_t_to_v(self.xt) self.yv = self.grd.change_grid_location_t_to_v(self.yt) self.tvar1 = xu.sin(self.xt * self.scale) + xu.cos(self.yt * self.scale) self.uvar1 = xu.sin(self.xu * self.scale) + xu.cos(self.yu * self.scale) self.vvar1 = xu.sin(self.xv * self.scale) + xu.cos(self.yv * self.scale) self.vector = grids.VectorField2d(self.uvar1 ,self.vvar1, x_component_grid_location = 'u', y_component_grid_location = 'v') self.tvar2 = xu.cos(self.yt * self.scale)
def test_horizontal_laplacian(self): tols = {'rtol': 1e-3, 'atol': 1e-3} l = self.grd.horizontal_laplacian(self.tvar2) s = self.scale print s actual_lap = l.to_masked_array() expected_lap = (-1. * xu.cos(self.yt * s) * s * s).to_masked_array() #print_array_around(expected=expected_lap/s**2,actual=actual_lap/s**2) self.assertArray2dCloseInside(actual_lap / s**2, expected_lap / s**2, depth=4, **tols)
def __call__(self, projectables, **kwargs): day_data = projectables[0] night_data = projectables[1] lim_low = np.cos(np.deg2rad(self.lim_low)) lim_high = np.cos(np.deg2rad(self.lim_high)) try: coszen = xu.cos(xu.deg2rad(projectables[2])) except IndexError: from pyorbital.astronomy import cos_zen LOG.debug("Computing sun zenith angles.") # Get chunking that matches the data try: chunks = day_data.sel(bands=day_data['bands'][0]).chunks except KeyError: chunks = day_data.chunks lons, lats = day_data.attrs["area"].get_lonlats_dask(chunks) coszen = xr.DataArray(cos_zen(day_data.attrs["start_time"], lons, lats), dims=['y', 'x'], coords=[day_data['y'], day_data['x']]) # Calculate blending weights coszen -= np.min((lim_high, lim_low)) coszen /= np.abs(lim_low - lim_high) coszen = coszen.clip(0, 1) # Apply enhancements to get images day_data = enhance2dataset(day_data) night_data = enhance2dataset(night_data) # Adjust bands so that they match # L/RGB -> RGB/RGB # LA/RGB -> RGBA/RGBA # RGB/RGBA -> RGBA/RGBA day_data = add_bands(day_data, night_data['bands']) night_data = add_bands(night_data, day_data['bands']) # Get merged metadata attrs = combine_metadata(day_data, night_data) # Blend the two images together data = (1 - coszen) * night_data + coszen * day_data data.attrs = attrs # Split to separate bands so the mode is correct data = [data.sel(bands=b) for b in data['bands']] res = super(DayNightCompositor, self).__call__(data, **kwargs) return res
def setUp(self): # only testing on a regular grid x = np.arange(start=0, stop=1.e7, step=1.e5, dtype=float) y = np.arange(start=0, stop=1.2e7, step=1.e5, dtype=float) x, y = np.meshgrid(x, y) self.grd = agrids.plane_2d_grid(ycoord=y, xcoord=x) self.scale = 2. * 3.14159 / 1.e7 self.xt = self.grd._arrays['plane_x_coordinate_at_t_location'] self.yt = self.grd._arrays['plane_y_coordinate_at_t_location'] self.xu = self.grd.change_grid_location_t_to_u(self.xt) self.yu = self.grd.change_grid_location_t_to_u(self.yt) self.xv = self.grd.change_grid_location_t_to_v(self.xt) self.yv = self.grd.change_grid_location_t_to_v(self.yt) self.tvar1 = xu.sin(self.xt * self.scale) + xu.cos( self.yt * self.scale) self.uvar1 = xu.sin(self.xu * self.scale) + xu.cos( self.yu * self.scale) self.vvar1 = xu.sin(self.xv * self.scale) + xu.cos( self.yv * self.scale) self.vector = grids.VectorField2d(self.uvar1, self.vvar1, x_component_grid_location='u', y_component_grid_location='v') self.tvar2 = xu.cos(self.yt * self.scale)
def _prep_overturning(inputfield, lat=None, lev=None, levax=0): if lat is None: try: lat = inputfield.lat except: raise ValueError('Need to supply latitude array if input data is not self-describing.') if lev is None: try: lev = inputfield.lev except: raise ValueError('Need to supply pressure array if input data is not self-describing.') lat_rad = deg2rad(lat) coslat = cos(lat_rad) field = inputfield * coslat try: levax = field.get_axis_num('lev') except: pass return field, lat, lev, levax
def test_horizontal_gradient(self): tols = {'rtol':1e-3,'atol':1e-3} gradvar = self.grd.horizontal_gradient(self.tvar1) gradx = self.grd.horizontal_gradient(self.xt) #dxdy = gradx.y_component s = self.scale actual_gx = gradvar.x_component.to_masked_array() actual_gy = gradvar.y_component.to_masked_array() expected_gx = (xu.cos(self.xu * s) * s).to_masked_array() expected_gy = (- xu.sin(self.yv * s) * s # + xu.cos(self.xv * s) * s * dxdy ).to_masked_array() self.assertArray2dCloseInside(actual_gx / s,expected_gx / s , depth=2,**tols) #print dxdy[:,20].values #print self.grd._arrays['cell_y_size_at_t_location'][:,20].values #print self.grd._arrays['cell_y_size_at_v_location'][:,20].values #print_array_around(expected=expected_gy/s,actual=actual_gy/s) self.assertArray2dCloseInside(actual_gy / s,expected_gy /s , depth=2, **tols)
def sunzen_corr_cos(data, cos_zen, limit=88.): """Perform Sun zenith angle correction. The correction is based on the provided cosine of the zenith angle (*cos_zen*). The correction is limited to *limit* degrees (default: 88.0 degrees). For larger zenith angles, the correction is the same as at the *limit*. Both *data* and *cos_zen* are given as 2-dimensional Numpy arrays or Numpy MaskedArrays, and they should have equal shapes. """ # Convert the zenith angle limit to cosine of zenith angle limit = xu.cos(xu.deg2rad(limit)) # Cosine correction corr = 1. / cos_zen # Use constant value (the limit) for larger zenith # angles corr = corr.where(cos_zen > limit).fillna(1 / limit) return data * corr
def __call__(self, projectables, **info): projectables = self.check_areas(projectables) vis = projectables[0] if vis.attrs.get("sunz_corrected"): LOG.debug("Sun zen correction already applied") return vis if hasattr(vis.attrs["area"], 'name'): area_name = vis.attrs["area"].name else: area_name = 'swath' + str(vis.shape) key = (vis.attrs["start_time"], area_name) tic = time.time() LOG.debug("Applying sun zen correction") if len(projectables) == 1: coszen = self.coszen.get(key) if coszen is None: from pyorbital.astronomy import cos_zen LOG.debug("Computing sun zenith angles.") lons, lats = vis.attrs["area"].get_lonlats_dask(CHUNK_SIZE) coszen = xr.DataArray(cos_zen(vis.attrs["start_time"], lons, lats), dims=['y', 'x'], coords=[vis['y'], vis['x']]) coszen = coszen.where((coszen > 0.035) & (coszen < 1)) self.coszen[key] = coszen else: coszen = xu.cos(xu.deg2rad(projectables[1])) self.coszen[key] = coszen proj = self._apply_correction(vis, coszen) proj.attrs = vis.attrs.copy() self.apply_modifier_info(vis, proj) LOG.debug( "Sun-zenith correction applied. Computation time: %5.1f (sec)", time.time() - tic) return proj
def atmospheric_path_length_correction(data, cos_zen, limit=88.): """Perform Sun zenith angle correction. This function uses the correction method proposed by Li and Shibata (2006): https://doi.org/10.1175/JAS3682.1 The correction is limited to *limit* degrees (default: 88.0 degrees). For larger zenith angles, the correction is the same as at the *limit*. Both *data* and *cos_zen* are given as 2-dimensional Numpy arrays or Numpy MaskedArrays, and they should have equal shapes. """ # Convert the zenith angle limit to cosine of zenith angle limit = xu.cos(xu.radians(limit)) # Cosine correction corr = _get_sunz_corr_li_and_shibata(cos_zen) # Use constant value (the limit) for larger zenith # angles corr_lim = _get_sunz_corr_li_and_shibata(limit) corr = corr.where(cos_zen > limit).fillna(corr_lim) return data * corr
def test_pickle(self): a = 1.0 cos_pickled = pickle.loads(pickle.dumps(xu.cos)) self.assert_identical(cos_pickled(a), xu.cos(a))
def test_xarray_ufuncs_pickle(): a = 1.0 cos_pickled = pickle.loads(pickle.dumps(xu.cos)) assert_identical(cos_pickled(a), xu.cos(a))
def test_xarray_ufuncs_deprecation(): with pytest.warns(FutureWarning, match="xarray.ufuncs"): xu.cos(xr.DataArray([0, 1])) with assert_no_warnings(): xu.angle(xr.DataArray([0, 1]))
def test_xarray_ufuncs_deprecation(): with pytest.warns(PendingDeprecationWarning, match='xarray.ufuncs'): xu.cos(xr.DataArray([0, 1]))
def run_crefl(refl, coeffs, lon, lat, sensor_azimuth, sensor_zenith, solar_azimuth, solar_zenith, avg_elevation=None, percent=False): """Run main crefl algorithm. All input parameters are per-pixel values meaning they are the same size and shape as the input reflectance data, unless otherwise stated. :param reflectance_bands: tuple of reflectance band arrays :param coefficients: tuple of coefficients for each band (see `get_coefficients`) :param lon: input swath longitude array :param lat: input swath latitude array :param sensor_azimuth: input swath sensor azimuth angle array :param sensor_zenith: input swath sensor zenith angle array :param solar_azimuth: input swath solar azimuth angle array :param solar_zenith: input swath solar zenith angle array :param avg_elevation: average elevation (usually pre-calculated and stored in CMGDEM.hdf) :param percent: True if input reflectances are on a 0-100 scale instead of 0-1 scale (default: False) """ # FUTURE: Find a way to compute the average elevation before hand (ah2o, bh2o, ao3, tau) = coeffs # Get digital elevation map data for our granule, set ocean fill value to 0 if avg_elevation is None: LOG.debug("No average elevation information provided in CREFL") #height = np.zeros(lon.shape, dtype=np.float) height = 0. else: row = ((90.0 - lat) * avg_elevation.shape[0] / 180.0).astype(np.int32) col = ((lon + 180.0) * avg_elevation.shape[1] / 360.0).astype(np.int32) height = avg_elevation[row, col].astype(np.float64) # negative heights aren't allowed, clip to 0 height = height.where(height >= 0., 0.0) del lat, lon, row, col mus = xu.cos(xu.deg2rad(solar_zenith)) muv = xu.cos(xu.deg2rad(sensor_zenith)) phi = solar_azimuth - sensor_azimuth del solar_azimuth, solar_zenith, sensor_zenith, sensor_azimuth sphalb, rhoray, TtotraytH2O, tOG = get_atm_variables( mus, muv, phi, height, (ah2o, bh2o, ao3, tau)) if rhoray.shape[1] != refl.shape[1]: LOG.debug( "Interpolating CREFL calculations for higher resolution bands") # Assume we need to interpolate # FIXME: Do real bilinear interpolation instead of "nearest" factor = int(refl.shape[1] / rhoray.shape[1]) rhoray = np.repeat(np.repeat(rhoray, factor, axis=0), factor, axis=1) tOG = np.repeat(np.repeat(tOG, factor, axis=0), factor, axis=1) TtotraytH2O = np.repeat(np.repeat(TtotraytH2O, factor, axis=0), factor, axis=1) # if average height wasn't provided then this should stay a scalar if sphalb.size != 1: # otherwise make it the same size as the other arrays sphalb = np.repeat(np.repeat(sphalb, factor, axis=0), factor, axis=1) # Note: Assume that fill/invalid values are either NaN or we are dealing # with masked arrays if percent: corr_refl = ((refl / 100.) / tOG - rhoray) / TtotraytH2O else: corr_refl = (refl / tOG - rhoray) / TtotraytH2O corr_refl /= (1.0 + corr_refl * sphalb) return corr_refl.clip(REFLMIN, REFLMAX)
def test_pickle(self): a = 1.0 cos_pickled = pickle.loads(pickle.dumps(xu.cos)) self.assertIdentical(cos_pickled(a), xu.cos(a))