Source code for pedophysics.simulate

import numpy as np
import pandas as pd

[docs]class Soil(object): """ A class to represent a soil sample with its characteristics. Attributes ---------- temperature : array-like Soil bulk temperature [K] water : array-like Soil volumetric water content [m**3/m**3] salinity : array-like Soil salinity (NaCl) of the bulk pore fluid [mol/L] sand : array-like Soil sand content [g/g]*100 silt : array-like Soil silt content [g/g]*100 clay : array-like Soil clay content [g/g]*100 porosity : array-like Soil porosity [m**3/m**3] bulk_density : array-like Soil bulk density [kg/m**3] particle_density : array-like Soil particle density [kg/m**3] CEC : array-like Soil cation exchange capacity [meq/100g] orgm : array-like Soil organic matter [g/g]*100 bulk_perm : array-like Soil bulk real relative dielectric permittivity [-] bulk_perm_inf : array-like Soil bulk real relative permittivity at infinite frequency [-] water_perm : array-like Soil water phase real dielectric permittivity [-] solid_perm : array-like Soil solid real relative dielectric permittivity phase [-] air_perm : array-like Soil air real relative dielectric permittivity phase [-] offset_perm : array-like Soil bulk real relative dielectric permittivity when soil bulk real electrical conductivity is zero [-] bulk_ec : array-like Soil bulk real electrical conductivity [S/m] bulk_ec_tc : array-like Soil bulk real electrical conductivity temperature corrected (298.15 K) [S/m] bulk_ec_dc : array-like Soil bulk real electrical conductivity direct current [S/m] bulk_ec_dc_tc : array-like Soil bulk real electrical conductivity direct current (0 Hz) temperature corrected (298.15 K) [S/m] water_ec : array-like Soil water real electrical conductivity [S/m] s_ec : array-like Soil bulk real surface electrical conductivity [S/m] solid_ec : array-like Soil solid real electrical conductivity [S/m] dry_ec : array-like Soil bulk real electrical conductivity at zero water content [S/m] sat_ec : array-like Soil bulk real electrical conductivity at saturation water content [S/m] frequency_perm : array-like Frequency of dielectric permittivity measurement [Hz] frequency_ec : array-like Frequency of electric conductivity measurement [Hz] L : single-value Soil scalar depolarization factor of solid particles (effective medium theory) [-] Lw : single-value Soil scalar depolarization factor of water aggregates (effective medium theory) [-] m : single-value Soil cementation factor as defined in Archie law [-] n : single-value Soil saturation factor as defined in Archie second law [-] alpha : single-value Soil alpha exponent as defined in volumetric mixing theory [-] texture : str Soil texture according to USDA convention: "Sand", "Loamy sand", "Sandy loam", "Loam", "Silt loam", "Silt", "Sandy clay loam", "Clay loam", "Silty clay loam", "Sandy clay", "Clay", "Silty clay" instrument : str Instrument utilized: 'HydraProbe', 'TDR', 'GPR', 'Miller 400D', 'Dualem' info : DataFrame Data Frame containing descriptive information about how each array-like attribute was determined or modified. df : DataFrame Data Frame containing the quantitative information of all soil array-like attributes for each state. E : single-value Empirical constant as in Rohades model [-] F : single-value Empirical constant as in Rohades model [-] roundn : int Number of decimal places to round results. range_ratio : single-value Factor for extending extrapolation domain during fitting modelling n_states : int Number of soil states Notes ----- Attributes provided by the user that do not match the expected types or values will raise a ValueError. """ def __init__(self, **kwargs): # Define acceptable types for each argument array_like_types = [float, np.float64, int, list, np.ndarray] single_value = [float, np.float64, int] attributes = { 'temperature': array_like_types, 'water': array_like_types, 'salinity': array_like_types, 'sand': array_like_types, 'silt': array_like_types, 'clay': array_like_types, 'porosity': array_like_types, 'bulk_density': array_like_types, 'particle_density': array_like_types, 'CEC': array_like_types, 'orgm': array_like_types, 'bulk_perm': array_like_types, 'bulk_perm_inf': array_like_types, 'air_perm': array_like_types, 'water_perm': array_like_types, 'solid_perm': array_like_types, 'offset_perm': array_like_types, 'bulk_ec': array_like_types, 'bulk_ec_tc': array_like_types, 'bulk_ec_dc': array_like_types, 'bulk_ec_dc_tc': array_like_types, 'water_ec': array_like_types, 'solid_ec': array_like_types, 'dry_ec': array_like_types, 'sat_ec': array_like_types, 's_ec': array_like_types, 'frequency_perm': array_like_types, 'frequency_ec': array_like_types, 'L': single_value, 'Lw': single_value, 'm': single_value, 'n': single_value, 'alpha': single_value, 'texture': [str], 'instrument': [str], 'range_ratio': single_value, 'n_states': single_value, 'E': single_value, 'F': single_value, 'roundn': [int] } accepted_values = { 'texture': ["Sand", "Loamy sand", "Sandy loam", "Loam", "Silt loam", "Silt", "Sandy clay loam", "Clay loam", "Sandy clay", "Clay", "Silty clay", np.nan], 'instrument': ["TDR", "GPR", 'HydraProbe', 'EMI Dualem', 'EMI EM38-DD', np.nan] } # Convert all inputs to np.ndarray if they are of type list, int, or float def to_ndarray(arg, key=None): if key in ['texture', 'instrument']: return arg # return the argument if it is 'texture' or 'instrument' if isinstance(arg, (list, int, np.float64, float)): return np.array([arg]) if isinstance(arg, (int, np.float64, float)) else np.array(arg) return arg # Check each input argument for key in attributes: if key in kwargs: value = kwargs[key] if type(value) in attributes[key]: # if the key is 'texture' or 'instrument' verify if value is in the accepted_values if key in ['texture', 'instrument'] and value not in accepted_values[key]: raise ValueError(f"Invalid value for '{key}'. Must be one of {accepted_values[key]}") setattr(self, key, to_ndarray(value, key=key)) else: raise ValueError(f"'{key}' must be one of {attributes[key]}") else: # If the key is not provided in the kwargs, set it as np.nan. setattr(self, key, to_ndarray(np.nan, key=key)) self.roundn = 3 if np.isnan(self.roundn[0]) else self.roundn self.range_ratio = 2 if np.isnan(self.range_ratio[0]) else self.range_ratio ### Fill the state variables with nans when are shorter than n_states array_like_attributes = ['temperature', 'water', 'salinity', 'sand', 'silt', 'clay', 'porosity', 'bulk_density', 'particle_density', 'CEC', 'orgm', 'bulk_perm', 'bulk_perm_inf', 'air_perm', 'water_perm', 'solid_perm', 'offset_perm', 'bulk_ec', 'bulk_ec_tc', 'bulk_ec_dc', 'bulk_ec_dc_tc', 'water_ec', 'solid_ec', 'dry_ec', 'sat_ec', 's_ec', 'frequency_perm', 'frequency_ec'] # calculate the max length of the input arrays n_states = max([len(getattr(self, attr)) for attr in array_like_attributes]) self.n_states = n_states # Number of states of the soil # Now loop over each attribute in the list for attribute in array_like_attributes: attr = getattr(self, attribute) if len(attr) != n_states: setattr(self, attribute, np.append(attr, [np.nan]*(n_states - len(attr)))) if ~np.isnan(attr[0]) and (np.isnan(attr[1:(n_states)])).all(): setattr(self, attribute, np.append(attr[0], [attr[0]]*(n_states - 1))) ### Defining special attributes ### self.df = pd.DataFrame({attr: getattr(self, attr) for attr in array_like_attributes}) # defining soil.info self.info = self.df.where(pd.notna(self.df), 'nan') self.info = self.info.where(pd.isna(self.df), 'Value given by the user') # Simplify the getter methods using __getattr__ def __getattr__(self, name): """ Custom attribute access mechanism. Parameters ---------- name : str Name of the attribute to be accessed. Returns ------- np.ndarray The value of the attribute. Raises ------ AttributeError If the attribute does not exist. """ if name in self.__dict__: return self.__dict__[name] else: raise AttributeError(f"No such attribute: {name}") def __str__(self): """ Return a string representation of the class. Returns ------- str String representation of the class as Soil.df """ return str(self.df)