def process_coherent_layers(snowpack, emmodel_list, sensor): effective_permittivity = [em.effective_permittivity() for em in emmodel_list] phase = [sensor.wavenumber * np.sqrt(eps_eff).real * lay.thickness for lay, eps_eff in zip(snowpack.layers, effective_permittivity)] coherent_layers = np.array(phase) < 3 * np.pi / 4 if not np.any(coherent_layers): return snowpack, emmodel_list snowpack = snowpack.copy() if coherent_layers[-1]: raise SMRTError("The last layer is coherent, this is not supported") print("process_coherent_layers (in dev, use for testing only) # coherent layers:", np.sum(coherent_layers)) for l in np.flatnonzero(coherent_layers[:-1])[::-1]: # reverse the processing to safely delete the snowpack layer and interface print("coherent layer:", l) if coherent_layers[l - 1]: raise SMRTError("Two sucessive layers are coherent, this is not yet supported") # create a coherent interface coherent_interface = CoherentFlat(snowpack.interfaces[l:l + 2], snowpack.layers[l], effective_permittivity[l]) # set the next interface to coherent snowpack.interfaces[l + 1] = coherent_interface # delete the layer to be deleted snowpack.delete(l) # delete layer and interface l emmodel_list.pop(l) return snowpack, emmodel_list
def sel(self, **kwargs): if 'mode' in kwargs: if self.mtype == "dense5": if self.values.shape[0] == 3 and kwargs[ 'auto_reduce_npol'] and kwargs['mode'] == 0: ## 3pol->2pol return smrt_matrix(self.values[0:2, 0:2, kwargs['mode'], :, :], mtype='dense4') else: return smrt_matrix(self.values[:, :, kwargs['mode'], :, :], mtype='dense4') elif self.mtype == "diagonal5": if self.values.shape[0] == 3 and kwargs[ 'auto_reduce_npol'] and kwargs['mode'] == 0: ## 3pol->2pol return smrt_matrix(self.values[0:2, kwargs['mode'], :], mtype='diagonal4') else: return smrt_matrix(self.values[:, kwargs['mode'], :], mtype='diagonal4') elif self.mtype == "dense4": raise SMRTError("Dense4 matrix can not be selected by mode") elif self.mtype == "diagonal4": raise SMRTError("Diagonal4 matrix can not be selected by mode") else: raise NotImplementedError else: raise SMRTError("Currently only selection by mode is implemented")
def common_amsr(sensor_name, frequency_dict, channel=None, frequency=None, polarization=None, theta=55): if isinstance(channel, Sequence) and not isinstance(channel, str): if frequency is not None: raise SMRTError("Either channel or frequency should be given. Mixing both arguments is not understood.") return SensorList([common_amsr(sensor_name, frequency_dict, channel=c, frequency=None, polarization=polarization, theta=theta) for c in channel]) if channel is not None: if len(channel) == 3: polarization = channel[2] else: polarization = ['H', 'V'] fch = channel[0:2] try: frequency = frequency_dict[fch] except KeyError: raise SMRTError("%s channel frequency not recognized. Expected one of: %s" % (sensor_name, ", ".join(frequency_dict.keys()))) if frequency is None: frequency = sorted(set(frequency_dict.values())) polarization = ['H', 'V'] sensor = Sensor(frequency, None, theta, None, None, polarization, channel) return sensor
def sel(self, **kwargs): if 'mode' in kwargs: mode = kwargs['mode'] # 3pol->2pol if self.values.shape[0] == 3 and kwargs[ 'auto_reduce_npol'] and mode == 0: pola = slice(0, 2) else: pola = slice(None) if self.mtype == "dense5": return smrt_matrix(self.values[pola, pola, mode, :, :], mtype='dense4') elif self.mtype == "diagonal5": return smrt_matrix(self.values[pola, mode, :], mtype='diagonal4') elif self.mtype == "dense4": raise SMRTError("Dense4 matrix can not be selected by mode") elif self.mtype == "diagonal4": raise SMRTError("Diagonal4 matrix can not be selected by mode") else: raise NotImplementedError else: raise SMRTError("Currently only selection by mode is implemented")
def import_class(scope, modulename, classname=None): """Import the modulename and return either the class named "classname" or the first class defined in the module if classname is None. :param scope: scope where to search for the module. :param modulename: name of the module to load. :param classname: name of the class to read from the module. """ if (".." in modulename) or (modulename[0] == '.'): raise SMRTError("modulename error. Relative import is not allowed") modulename = scope + "." + modulename # add user_directories for pkg in user_plugin_package: #print(pkg + "." + modulename) res = do_import_class(pkg + "." + modulename, classname) if res is not None: return res # the last case, search in the smrt package res = do_import_class("smrt." + modulename, classname) if res is None: if classname is None: msg = "Unable to find the module '%s'." % modulename else: msg = "Unable to find the module '%s' to import the class '%s'." % ( modulename, classname) raise SMRTError(msg) return res
def make_soil(substrate_model, permittivity_model, temperature, moisture=None, sand=None, clay=None, drymatter=None, **kwargs): """ Construct a soil instance based on a given surface electromagnetic model, a permittivity model and parameters :param substrate_model: name of substrate model, can be a class or a string. e.g. fresnel, wegmuller... :param permittivity_model: permittivity_model to use. Can be a name ("hut_epss" or "dobson85"), a function of frequency and temperature or a complex value. :param moisture: soil moisture in m:sup:`3` m:sup:`-3` to compute the permittivity. This parameter is used depending on the permittivity_model. :param sand: soil relative sand content. This parameter is used or not depending on the permittivity_model. :param clay: soil relative clay content. This parameter is used or not depending on the permittivity_model. :param drymatter: soil content in dry matter in kg m:sup:`-3`. This parameter is used or not depending on the permittivity_model. :param **kwargs: geometrical parameters depending on the substrate_model. Refer to the document of each model to see the list of required and optional parameters. Usually, it is roughness_rms, corr_length, ... **Usage example:** :: TOTEST: bottom = substrate.make('Flat', permittivity_model=complex('6-0.5j')) TOTEST: bottom = substrate.make('Wegmuller', permittivity_model='soil', roughness_rms=0.25, moisture=0.25) """ # process the permittivity_model argument if isinstance(permittivity_model, str): if sand is None or clay is None or drymatter is None: raise SMRTError("The parameters sand, clay and drymatter must be set") if permittivity_model == "hut_epss": # return soil_dielectric_constant_hut after setting the parameters permittivity_model = partial(soil_dielectric_constant_hut, SM=moisture, sand=sand, clay=clay, dm_rho=drymatter) elif permittivity_model == "dobson85": # return soil_dielectric_constant_dobson after setting the parameters permittivity_model = partial(soil_dielectric_constant_dobson, SM=moisture, S=sand, C=clay) else: raise SMRTError("The permittivity model '%s' is not recongized" % permittivity_model) else: if isinstance(permittivity_model, Number): # a constant value # create a function with 2 args that always return the same value permittivity_model = lambda frequency, temperature, cst=permittivity_model: cst elif not callable(permittivity_model): raise SMRTError("The permittivity_model argument is not of the accepted types." "It must be a string with an implemented permittivity model name," " a number or a function with two arguments.") # check that other parameters are if moisture is not None or sand is not None or clay is not None or drymatter is not None: raise Warning("Setting moisture, clay, sand or drymatter when permittivity_model is a number or function is useless") # process the substrate_model argument if not isinstance(substrate_model, type): substrate_model = get_substrate_model(substrate_model) # create the instance return substrate_model(temperature, permittivity_model, **kwargs)
def make_snowpack(thickness, microstructure_model, density, interface=None, surface=None, substrate=None, atmosphere=None, **kwargs): """ build a multi-layered snowpack. Each parameter can be an array, list or a constant value. :param thickness: thicknesses of the layers in meter (from top to bottom). The last layer thickness can be "numpy.inf" for a semi-infinite layer. :param microstructure_model: microstructure_model to use (e.g. sticky_hard_spheres or independent_sphere or exponential). :param surface: type of surface interface, flat/fresnel is the default. If surface and interface are both set, the interface must be a constant refering to all the "internal" interfaces. :param interface: type of interface, flat/fresnel is the default. It is usually a string for the interfaces without parameters (e.g. Flat or Transparent) or is created with :py:func:`~smrt.core.interface.make_interface` in more complex cases. Interface can be a constant or a list. In the latter case, its length must be the same as the number of layers, and interface[0] refers to the surface interface. :param density: densities of the layers. :param substrate: set the substrate of the snowpack. Another way to add a substrate is to use the + operator (e.g. snowpack + substrate). :param **kwargs: All the other parameters (temperature, microstructure parameters, emmodel, etc.) are given as optional arguments (e.g. temperature=[270, 250]). They are passed for each layer to the function :py:func:`~smrt.inputs.make_medium.make_snow_layer`. Thus, the documentation of this function is the reference. It describes precisely the available parameters. The microstructure parameter(s) depend on the microstructure_model used and is documented in each microstructure_model module. e.g.:: sp = make_snowpack([1, 10], "exponential", density=[200,300], temperature=[240, 250], corr_length=[0.2e-3, 0.3e-3]) """ sp = Snowpack(substrate=substrate, atmosphere=atmosphere) if not isinstance(thickness, collections.abc.Iterable): raise SMRTError("The thickness argument must be iterable, that is, a list of numbers, numpy array or pandas Series or DataFrame.") lib.check_argument_size(density, len(thickness), "density") lib.check_argument_size(kwargs, len(thickness)) if surface is not None and lib.is_sequence(interface): raise SMRTError("Setting both 'surface' and 'interface' arguments is ambiguous when inteface is a list or any sequence.") for i, dz in enumerate(thickness): layer = make_snow_layer(dz, lib.get(microstructure_model, i, "microstructure_model"), density=lib.get(density, i, "density"), **lib.get(kwargs, i)) # add the interface linterface = lib.get(interface, i, "interface") if (i > 0) or (surface is None) else surface sp.append(layer, interface=make_interface(linterface)) return sp
def check_validity(self, ks, kl, eps_r): # check validity if ks > 3: raise SMRTError( "Warning, roughness_rms is too high for the given wavelength. Limit is ks < 3. Here ks=%g" % ks) if ks * kl > np.sqrt(eps_r): raise SMRTError( "Warning, roughness_rms or correlation_length are too high for the given wavelength." " Limit is ks * kl < sqrt(eps_r). Here ks*kl=%g and sqrt(eps_r)=%g" % (ks * kl, np.sqrt(eps_r)))
def __init__(self, mat, mtype=None): if mat is 0: self.values = np.float64(0.) # 0, but can be used as a numpy thing self.mtype = "0" else: self.values = mat if mtype is None: if isinstance(mat, list) and len(mat) in [2, 3]: # diagonal matrix if len(mat[0].shape) == 2: mtype = "diagonal5" else: mtype = "diagonal4" elif len(mat.shape) == 5: mtype = "dense5" elif len(mat.shape) == 4: mtype = "dense4" elif len(mat.shape) == 3: mtype = "diagonal5" elif len(mat.shape) == 2: mtype = "diagonal4" else: raise SMRTError("Unsupported matrix size") self.mtype = mtype
def check_validity(self, ks, kl, eps_r): # check validity if ks > 3: raise SMRTError( "Warning, roughness_rms is too high for the given wavelength. Limit is ks < 3. Here ks=%g" % ks)
def amsre(channel=None, frequency=None, polarization=None, theta=55): """ Configuration for AMSR-E sensor. This function can be used to simulate all 12 AMSR-E channels i.e. frequencies of 6.925, 10.65, 18.7, 23.8, 36.5 and 89 GHz at both polarizations H and V. Alternatively single channels can be specified with 3-character identifiers. 18 and 19 GHz can be used interchangably to represent 18.7 GHz, similarly either 36 and 37 can be used to represent the 36.5 GHz channel. :param channel: single channel identifier :type channel: 3-character string :returns: :py:class:`Sensor` instance **Usage example:** :: from smrt import sensor radiometer = sensor.amsre() # Simulates all channels radiometer = sensor.amsre('36V') # Simulates 36.5 GHz channel only radiometer = sensor.amsre('06H') # 6.925 GHz channel """ amsre_frequency_dict = { '06': 6.925e9, '10': 10.65e9, '18': 18.7e9, '23': 23.8e9, '36': 36.5e9, '89': 89e9 } if channel is not None: if len(channel) == 3: polarization = channel[2] else: polarization = ['H', 'V'] fch = channel[0:2] if fch == "19": # optional fch = "18" # optional if fch == "37": # optional fch = "36" # optional try: frequency = amsre_frequency_dict[fch] except KeyError: raise SMRTError( "AMSR-E channel frequency not recognized. Expected one of: 06, 10, 18 or 19, 23, 36 or 37, 89" ) if frequency is None: frequency = sorted(amsre_frequency_dict.values()) polarization = ['H', 'V'] sensor = Sensor(frequency, None, theta, None, None, polarization) return sensor
def decompose_channel(channel, lengths): if isinstance(channel, Sequence) and not isinstance(channel, six.string_types): data = [decompose_channel(ch) for ch in channel] frequency, theta, polarization, polarization_inc = tuple( map(list, zip(*data))) # transpose else: if len(channel) != sum(lengths): raise SMRTError("the channel has an incorrect length") if lengths[0] > 0: frequency = channel[0:lengths[0]] else: frequency = None if lengths[1] > 0: polarization = channel[lengths[0]] if lengths[1] == 2: polarization_inc = channel[lengths[0] + 1] else: polarization_inc = polarization = None if lengths[2] > 0: theta = float(channel[-lengths[2]:]) else: theta = None return frequency, theta, polarization, polarization_inc
def water_parameters(ice_type, **kwargs): """Make a semi-infinite water layer. :param ice_type: ice_type is used to determine if a saline or fresh water layer is added Optional arguments are 'water_temperature', 'water_salinity' and 'water_depth' of the water layer. """ # prepare default if ice_type in ['firstyear', 'multiyear']: water_temperature = FREEZING_POINT - 1.8 water_salinity = 0.032 # = 0.032kg/kg = 32PSU; somewhat arbitrary value, fresher than average ocean salinity, reflecting lower salinities in polar regions elif ice_type == 'fresh': water_temperature = FREEZING_POINT water_salinity = 0. else: raise SMRTError( "'medium' must be set to one of the following: True (default), 'ocean', 'fresh'. Additional optional arguments for function make_ice_column are 'water_temperature', 'water_salinity' and 'water_depth'." ) water_depth = 10. # arbitrary value of 10m thickness for the water layer, microwave absorption in water is usually high, so this represents an infinitely thick water layer # override the following variable if set WaterParameter = collections.namedtuple( "WaterParameter", ('water_temperature', 'water_salinity', 'water_permittivity_model')) wp = WaterParameter(water_temperature=kwargs.get('water_temperature', water_temperature), water_salinity=kwargs.get('water_salinity', water_salinity), water_permittivity_model=seawater_permittivity_klein76) return wp
def soil_dielectric_constant_monpetit2008(frequency, temperature): """Soil dielectric constant formulation based on the formulation Montpetit et al. 2018. The formulation is only valid for below-frrezing point temperature. Reference: Montpetit, B., Royer, A., Roy, A., & Langlois, A. (2018). In-situ passive microwave emission model parameterization of sub-arctic frozen organic soils. Remote Sensing of Environment, 205, 112–118. https://doi.org/10.1016/j.rse.2017.10.033 """ # from functools import partial # from smrt.inputs.make_soil import soil_dielectric_constant_dobson if temperature > 273.15: raise SMRTError( "soil_dielectric_constant_monpetit is not implemented for above freezing temperatures." ) # moisture=0.2 # sand=0.4 # clay=0.3 # return partial(soil_dielectric_constant_dobson, SM=moisture, S=sand, C=clay) # else: p = scipy.interpolate.interp1d( [10.65e9, 19e9, 37e9], [complex(3.18, 0.0061), complex(3.42, 0.0051), complex(4.47, 0.33)], fill_value="extrapolate") return p(frequency)
def do_import_class(modulename, classname): # check the module #spec = importlib.util.find_spec(modulename) #if spec is None: # return None # import the module try: module = importlib.import_module(modulename) except ModuleNotFoundError: return None if classname is None: # search for the first class defined in the module for name, obj in inspect.getmembers(module, inspect.isclass): if obj.__module__ == modulename: # the second condition check if the class was defined in this module classname = name break if classname is None: raise SMRTError("Unable to find a class in the module '%s'" % modulename) # get the class return getattr(module, classname)
def smap(mode, theta=40): """Configuration for the passive (mode=P) and active (mode=A) sensor on smap This function returns either a passive sensor at 1.4 GHz (L-band) sensor or an active sensor at 1.26 GHz. The incidence angle is 40°. """ if mode == 'P': return passive( 1.4e9, theta=theta, channel_map={pola: dict(polarization=pola) for pola in 'HV'}, name='smap') elif mode == 'A': return active(1.26e9, theta=theta, theta_inc=theta, channel_map={ channel: dict(polarization=channel[1], polarization_inc=channel[0]) for channel in ['HH', 'VV', 'HV'] }, name='smap') else: raise SMRTError('mode must by A (active) or P (passive')
def common_amsr(sensor_name, frequency_dict, channel=None, frequency=None, polarization=None, theta=55, name=None): if frequency is None: # take default values frequency = sorted(set(frequency_dict.values())) else: # recreate the frequency dict frequency_dict = { "%02i" % (freq * 1e9): freq for freq in np.atleast_1d(frequency) } if polarization is None: polarization = ['H', 'V'] # create the channel map channel_map = { freq + pola: dict(frequency=frequency_dict[freq], polarization=pola, theta=theta) for freq in frequency_dict for pola in polarization } if channel is not None: if isinstance(channel, str): channel = [channel] # add H and V to channel's name if not present new_channel = [] for ch in channel: if ch[-1] not in 'HV': new_channel += [ch + 'H', ch + 'V'] else: new_channel += [ch] # take into account 18=19 and 36=37 for ch in new_channel: if '18' in ch: channel_map[ch] = channel_map.pop('19' + ch[-1]) if '36' in ch: channel_map[ch] = channel_map.pop('37' + ch[-1]) try: channel_map = filter_channel_map(channel_map, new_channel) except KeyError: raise SMRTError("%s channel not recognized. Expected one of: %s" % (sensor_name, ", ".join(frequency_dict.keys()))) sensor = passive(channel_map=channel_map, **extract_configuration(channel_map), name=name) return sensor
def compute_thickness_from_z(z): """Compute the thickness of layers given the elevation z. Whatever the sign of z, the order *MUST* be from the topmost layer to the lowermost. Several situation are accepted and interpretated as follows: - z is positive and decreasing. The first value is the height of the surface about the ground (z=0) and z represents the top elevation of each layer. This is typical of the seasonal snowpack. - z is negative and decreasing. The first value is the elevation of the bottom of the first layer with respect to the surface (z=0). This is typical of a snowpack on ice-sheet. - z is positive and increasing. The first value is the depth of the bottom of the first layer with respect to the surface. This is typical of a snowpack on ice-sheet. - other case, when z is not monoton or is increasing with negative value raises an error. Because z indicate the top or the bottom of a layer depending whether z=0 is the ground or the surface, the value 0 can never be in z. This raises an error. """ order = (np.diff(z) < 0) if np.any(z == 0): raise SMRTError("z must not include 0") positive = z >= 0 if np.all(order): # descending z if np.all(positive): # z >0, this is typically a seasonal snowpack. z= height above ground z = -np.append(z.values, 0) else: # z < 0, this is typically a permanent, deep snowpack, without ground reference. z is the depth from the surface. z = -np.insert(z.values, 0, 0) elif np.any(order): # ascending z if np.all(positive): # ascending z and z > 0, this is typically a permanent, deep snowpack, without ground reference. # z is the depth from the surface. z = np.insert(z.values, 0, 0) else: # this is unusual raise SMRTError("z is ascending and has negative values, which an ambiguous situation") else: raise SMRTError("The z argument is not sorted") return np.diff(z)
def concat_results(result_list, coord): """Concatenate several results from :py:meth:`smrt.core.model.Model.run` (of type :py:class:`Result`) into a single result (of type :py:class:`Result`). This extends the number of dimension in the xarray hold by the instance. The new dimension is specified with coord :param result_list: list of results returned by :py:meth:`smrt.core.model.Model.run` or other functions. :param coord: a tuple (dimension_name, dimension_values) for the new dimension. Dimension_values must be a sequence or array with the same length as result_list. :returns: :py:class:`Result` instance """ if isinstance(coord, tuple): dim_name, dim_value = coord index = pd.Index(dim_value, name=dim_name) elif isinstance(coord, pd.Index): index = coord if index.name is None: index.name = 'snowpack_index' # hope this will not conflict with an existing column else: raise SMRTError('unknown type for the coord argument') ResultClass = type(result_list[0]) if not all([type(result) == ResultClass for result in result_list]): raise SMRTError("The results are not all of the same type") # channel_map ? if any((res.channel_map != result_list[0].channel_map for res in result_list)): assert isinstance(coord, tuple) # different channel maps, it means we have different sensors. Merge de sensor maps. channel_map = {ch: dict(**r.channel_map[ch], dim_name=dv) for r, dv in zip(result_list, dim_value) for ch in r.channel_map} else: # all the channel maps are the same channel_map = result_list[0].channel_map data = xr.concat([result.data for result in result_list], index) other_data = {v: xr.concat([result.other_data[v] for result in result_list], index) for v in result_list[0].other_data} return ResultClass(data, channel_map=channel_map, other_data=other_data)
def Tb_as_dataframe(self, index_by_channel=False, **kwargs): """Return brightness temperature. Any parameter can be added to slice the results (e.g. frequency=37e9 or polarization='V'). See xarray slicing with sel method (to document)""" tb = self.data.sel(**kwargs).to_dataframe(name='Tb') if not index_by_channel: return tb else: if 'channel' not in tb.index.names: raise SMRTError("No channel information is given in the result. Unable to index the result by channel.") return tb.set_index(np.array([str(channel) + str(pola) for channel, pola in tb.index]))
def sel_data(self, channel=None, return_backscatter=False, **kwargs): # this function allows selection as xarray.DataArray.sel and in addition by channel if a channel_map is defined. # ffilter the variables of channel_map[channel] that are effectively in self.data.dims # and apply them to the selector sel in addition to kwargs if channel is not None: kwargs.update({ k: v for k, v in self.channel_map[channel].items() if k in self.data.dims }) if return_backscatter: # get theta theta = kwargs.pop('theta', None) theta_inc = kwargs.pop('theta_inc', None) if theta is not None and theta_inc is not None: if not np.all(theta_inc == theta): raise SMRTError( 'theta and theta_inc must be the same when returning backscatter' ) if theta is None: theta = theta_inc if theta is None: theta = self.data.theta_inc def select_theta(x, theta, **kwargs): # select by theta and deal with cases where theta is in the coords or not if 'theta' in x.coords: return x.sel(theta=theta, theta_inc=theta, **kwargs) else: return x.sel(theta_inc=theta, **kwargs) if lib.is_sequence(theta): # now select all the theta if it is a sequence x = xr.concat([ select_theta(self.data, t, drop=True, **kwargs) for t in theta ], pd.Index(theta, 'theta_inc')) else: x = select_theta(self.data, theta, drop=True, **kwargs) else: x = self.data.sel(drop=True, **kwargs) if return_backscatter: x = (4 * np.pi * np.cos(np.deg2rad(theta))) * x return dB(x) if return_backscatter == "dB" else x else: return x
def get(x, i, name=None): # function to take the i-eme value in an array or dict of array. Can deal with scalar as well. In this case, it repeats the value. if isinstance(x, str): return x elif isinstance(x, pd.DataFrame) or isinstance(x, pd.Series): if i >= len(x.values): raise SMRTError( "The array '%s' is too short compared to the thickness array" % name) return x.values[i] if isinstance(x, Sequence) or isinstance(x, np.ndarray): if i >= len(x): raise SMRTError( "The array '%s' is too short compared to the thickness array." % name) return x[i] elif isinstance(x, dict): return {k: get(x[k], i, k) for k in x} else: return x
def register_package(pkg): global user_plugin_package # check that the package can be imported. It must have an __init__.py try: module = importlib.import_module(pkg) except ImportError as e: raise SMRTError( "The package must be in the the sys.path list and must contain a __init__.py file (even empty). The import error is %s" % str(e)) user_plugin_package.insert(0, pkg)
def maxwell_garnett(frac_volume, e0, eps, depol_xyz=None, inclusion_shape=None, length_ratio=None): """ Calculates effective permittivity using Maxwell-Garnett equation. :param frac_volume: Fractional volume of snow :param e0: Permittivity of background (no default, must be provided) :param eps: Permittivity of scattering material (no default, must be provided) :param depol_xyz: [Optional] Depolarization factors, spherical isotropy is default. It is not taken into account here. :param length_ratio: Length_ratio. Used to estimate depolarization factors when they are not given. :param inclusion_shape: Assumption for shape(s) of brine inclusions. Can be a string for single shape, or a list/tuple/dict of strings for mixture of shapes. So far, we have the following shapes: "spheres" and "random_needles" (i.e. randomly-oriented elongated ellipsoidal inclusions). If the argument is a dict, the keys are the shapes and the values are the mixing ratio. If it is a list, the mixing_ratio argument is required. :returns: random orientation effective permittivity **Usage example:** :: # If used by electromagnetic model module: from .commonfunc import maxwell_garnett effective_permittivity = maxwell_garnett(frac_volume=0.2, e0=1, eps=3.185, depol_xyz=[0.3, 0.3, 0.4]) # If accessed from elsewhere, use absolute import from smrt.emmodel.commonfunc import maxwell_garnett """ assert np.all(frac_volume <= 1) if inclusion_shape is not None and inclusion_shape != "spheres": raise SMRTError("inclusion_shape must be set to 'spheres'") if depol_xyz is None: depol_xyz = depolarization_factors(length_ratio) # Calculate x, y, z components of effective permittivity from Maxwell-Garnett theory effective_permittivity_xyz = e0 * (1 + frac_volume * (eps - e0) / (e0 + (1. - frac_volume) * depol_xyz * (eps - e0))) # Assume random orientation i.e. 1/3 of each polarizability component provides equal shares # to the macroscopic polarization density # See pg 68 Sihvola: Electromagnetic mixing formulas and applications return np.mean(effective_permittivity_xyz, dtype=np.complex128)
def return_as_dataframe(self, name, channel_axis=None, **kwargs): def xr_to_dataframe(x, name): # workaround for when the resulting array has no dims anymore if x.dims: return x.to_dataframe(name=name) else: return pd.DataFrame([float(x)], columns=[name]) if channel_axis in ["column", "index"]: if not self.channel_map: raise SMRTError( "No channel information is given in the result. Unable to index the result by channel." ) # concat the dataframe obtained for each channel x = pd.concat([ xr_to_dataframe(self.sel_data(channel=ch, **kwargs), name=ch) for ch in self.channel_map ], axis=1, join='inner') if channel_axis == "index": droplevel = not x.index.name and len(x.index) == 1 and x.index[ 0] == 0 # this is our added index, remove it x = x.stack() if isinstance(x, pd.Series): x = pd.DataFrame(x, columns=[name]) x.index.set_names('channel', level=-1) if droplevel: x = x.droplevel(0) return x elif channel_axis: raise SMRTError( 'channel_axis argument must be "column" or "index"') else: return xr_to_dataframe(self.sel_data(**kwargs), name=name)
def __init__(self, intensity, coords=None): """Construct results array with the given intensity array (numpy array or xarray) and dimensions if numpy array is given """ if isinstance(intensity, xr.DataArray): self.data = intensity else: self.data = xr.DataArray(intensity, coords) if hasattr(self, "mode"): self.data.attrs['mode'] = self.mode else: raise SMRTError("Result base class is abstract, uses a subclass instead. The subclass must define the 'mode' attribute")
def W_n(self, n, k): if self.autocorrelation_function == "gaussian": # gaussian C(r) = exp ( -(r/l)**2 ) l = self.corr_length return (l**2/(2*n)) * np.exp(-(k*l)**2/(4*n)) elif self.autocorrelation_function == "exponential": # exponential C(r) = exp( -r/l ) l = self.corr_length return (l/n)**2 * (1 + (k*l/n)**2)**(-1.5) else: raise SMRTError("The autocorrelation function must be expoential or gaussian")
def import_class(modulename, classname=None, root=None): """import the modulename and return either the class name classname or the first class defined in the module """ if root is not None: if "." in modulename: raise SMRTError( "modulename error. Composed module name is not allowed when root argument is used" ) modulename = root + "." + modulename # remove attempt of relative import if (".." in modulename) or (modulename[0] == '.'): raise SMRTError("modulename error. Relative import is not allowed") # import the module try: module = importlib.import_module(modulename) except ImportError as e: # TODO: try to import all the modules. Do we want this ?? raise SMRTError( "Unable to find the module '%s' to import the class '%s'. The error is \"%s\"" % (modulename, classname, str(e))) if classname is None: # search for the first class defined in the module for name, obj in inspect.getmembers(module): if inspect.isclass( obj ) and obj.__module__ == modulename: # the second condition check if the class was defined in this module classname = name break if classname is None: raise SMRTError("Unable to find a class in the module '%s'" % modulename) # get the class return getattr(module, classname)
def make_snowpack(thickness, microstructure_model, density, interface=None, substrate=None, **kwargs): """ build a multi-layered snowpack. Each parameter can be an array, list or a constant value. :param thickness: thicknesses of the layers in meter (from top to bottom). The last layer thickness can be "numpy.inf" for a semi-infinite layer. :param microstructure_model: microstructure_model to use (e.g. sticky_hard_spheres or independent_sphere or exponential). :param interface: type of interface, flat/fresnel is the default. :param density: densities of the layers. :param substrate: set the substrate of the snowpack. Another way to add a substrate is to use the + operator (e.g. snowpack + substrate). All the other parameters (temperature, microstructure parameters, emmodel, etc, etc) are given as optional arguments (e.g. temperature=[270, 250]). They are passed for each layer to the function :py:func:`~smrt.inputs.make_medium.make_snow_layer`. Thus, the documentation of this function is the reference. It describes precisely the available parameters. The microstructure parameter(s) depend on the microstructure_model used and is documented in each microstructure_model module. e.g.:: sp = make_snowpack([1, 10], "exponential", density=[200,300], temperature=[240, 250], corr_length=[0.2e-3, 0.3e-3]) """ sp = Snowpack(substrate=substrate) if not isinstance(thickness, collections.Iterable): raise SMRTError( "The thickness argument must be iterable, that is, a list of numbers, numpy array or pandas Series or DataFrame." ) lib.check_argument_size(density, len(thickness), "density") lib.check_argument_size(kwargs, len(thickness)) for i, dz in enumerate(thickness): layer = make_snow_layer(dz, lib.get(microstructure_model, i, "microstructure_model"), density=lib.get(density, i, "density"), **lib.get(kwargs, i)) # add the interface sp.append(layer, interface=make_interface(lib.get(interface, i, "interface"))) return sp
def make_generic_stack(thickness, temperature=273, ks=0, ka=0, effective_permittivity=1, interface=None, substrate=None, atmosphere=None): """ build a multi-layered medium with prescribed scattering and absorption coefficients and effective permittivity. Must be used with presribed_kskaeps emmodel. :param thickness: thicknesses of the layers in meter (from top to bottom). The last layer thickness can be "numpy.inf" for a semi-infinite layer. :param temperature: temperature of layers in K :param ks: scattering coefficient of layers in m^-1 :param ka: absorption coefficient of layers in m^-1 :param interface: type of interface, flat/fresnel is the default """ # TODO: Add an example # e.g.:: # # sp = make_snowpack([1, 10], "exponential", density=[200,300], temperature=[240, 250], corr_length=[0.2e-3, 0.3e-3]) # #""" sp = Snowpack(substrate=substrate, atmosphere=atmosphere) if not isinstance(thickness, collections.abc.Iterable): raise SMRTError( "The thickness argument must be iterable, that is, a list of numbers, numpy array or pandas Series or DataFrame." ) for i, dz in enumerate(thickness): layer = make_generic_layer(dz, ks=lib.get(ks, i, "ks"), ka=lib.get(ka, i, "ka"), effective_permittivity=lib.get( effective_permittivity, i, "effective_permittivity"), temperature=lib.get(temperature, i, "temperature")) sp.append(layer, lib.get(interface, i)) return sp